import { Geometry, Material } from "./WebGL";
import { Sphere, Cylinder, Triangle } from "./WebGL/shapes";
import { Vector3, XYZ } from "./WebGL/math";
import { clamp } from "./WebGL/math";
import { DoubleSide } from "./WebGL";
import { Color, CC, ColorSpec, Colored } from "./colors";
import { MarchingCube } from "./ProteinSurface4";
import { VolumeData } from "./VolumeData";
import { MeshDoubleLambertMaterial, MeshLambertMaterial, Object3D, Coloring, Mesh, LineBasicMaterial, Line, LineStyle } from "./WebGL";
import { CAP, GLDraw } from "./GLDraw"
import { subdivide_spline } from "./glcartoon";
import { adjustVolumeStyle, extend, Func, makeFunction } from "./utilities";
import { GradientType } from "./Gradient";
import { AtomSelectionSpec } from "specs";
import { GLViewer } from "GLViewer";
/**
* A GLShape is a collection of user specified shapes.
*
* @class
* @extends {ShapeSpec}
* @param {number} sid - Unique identifier
* @param {ShapeSpec} stylespec - shape style specification
*/
export class GLShape {
// Marching cube, to match with protein surface generation
private static ISDONE = 2;
private static finalizeGeo(geo) {
//to avoid creating a bunch of geometries, we leave geoGroup untruncated
//until render is called, at which point we truncate;
//successive called up updateGeo will return a new geometry
var geoGroup = geo.updateGeoGroup(0);
if (geoGroup.vertices > 0) {
geoGroup.truncateArrayBuffers(true, true);
}
};
/*
*
* @param {Geometry}
* geo
* @param {Color | colorlike} color
*/
static updateColor(geo: Geometry, color) {
color = color || CC.color(color);
geo.colorsNeedUpdate = true;
var r, g, b;
if (color.constructor !== Array) {
r = color.r;
g = color.g;
b = color.b;
}
for (let gg in geo.geometryGroups) {
let geoGroup = geo.geometryGroups[gg];
let colorArr = geoGroup.colorArray;
for (let i = 0, il = geoGroup.vertices; i < il; ++i) {
if (color.constructor === Array) {
let c = color[i];
r = c.r;
g = c.g;
b = c.b;
}
colorArr[i * 3] = r;
colorArr[i * 3 + 1] = g;
colorArr[i * 3 + 2] = b;
}
}
};
/*
* @param {GLShape}
* shape
* @param {geometryGroup}
* geoGroup
* @param {ArrowSpec}
* spec
*/
static drawArrow(shape: GLShape, geo: Geometry, spec: ArrowSpec) {
var from = spec.start, end = spec.end, radius = spec.radius,
radiusRatio = spec.radiusRatio, mid = spec.mid, midoffset = spec.midpos;
if (!(from && end))
return;
var geoGroup = geo.updateGeoGroup(51);
// vertices
var dir = new Vector3(end.x, end.y, end.z).sub(from);
if (midoffset) { //absolute offset, convert to relative
let length = dir.length();
if (midoffset > 0) mid = midoffset / length;
else mid = (length + midoffset) / length;
}
dir.multiplyScalar(mid);
var to = new Vector3(from.x, from.y, from.z).add(dir);
var negDir = dir.clone().negate();
let fromv = new Vector3(from.x, from.y, from.z);
shape.intersectionShape.cylinder.push(new Cylinder(fromv, to.clone(), radius));
shape.intersectionShape.sphere.push(new Sphere(fromv, radius));
// get orthonormal vector
var nvecs = [];
nvecs[0] = dir.clone();
if (Math.abs(nvecs[0].x) > 0.0001)
nvecs[0].y += 1;
else
nvecs[0].x += 1;
nvecs[0].cross(dir);
nvecs[0].normalize();
// another orth vector
nvecs[4] = nvecs[0].clone();
nvecs[4].crossVectors(nvecs[0], dir);
nvecs[4].normalize();
nvecs[8] = nvecs[0].clone().negate();
nvecs[12] = nvecs[4].clone().negate();
// now quarter positions
nvecs[2] = nvecs[0].clone().add(nvecs[4]).normalize();
nvecs[6] = nvecs[4].clone().add(nvecs[8]).normalize();
nvecs[10] = nvecs[8].clone().add(nvecs[12]).normalize();
nvecs[14] = nvecs[12].clone().add(nvecs[0]).normalize();
// eights
nvecs[1] = nvecs[0].clone().add(nvecs[2]).normalize();
nvecs[3] = nvecs[2].clone().add(nvecs[4]).normalize();
nvecs[5] = nvecs[4].clone().add(nvecs[6]).normalize();
nvecs[7] = nvecs[6].clone().add(nvecs[8]).normalize();
nvecs[9] = nvecs[8].clone().add(nvecs[10]).normalize();
nvecs[11] = nvecs[10].clone().add(nvecs[12]).normalize();
nvecs[13] = nvecs[12].clone().add(nvecs[14]).normalize();
nvecs[15] = nvecs[14].clone().add(nvecs[0]).normalize();
var start = geoGroup.vertices;
var vertexArray = geoGroup.vertexArray;
var faceArray = geoGroup.faceArray;
var normalArray = geoGroup.normalArray;
var lineArray = geoGroup.lineArray;
var offset, i, n;
// add vertices, opposing vertices paired together
for (i = 0, n = nvecs.length; i < n; ++i) {
offset = 3 * (start + 3 * i);
var bottom = nvecs[i].clone().multiplyScalar(radius).add(from);
var top = nvecs[i].clone().multiplyScalar(radius).add(to);
var conebase = nvecs[i].clone()
.multiplyScalar(radius * radiusRatio).add(to);
vertexArray[offset] = bottom.x;
vertexArray[offset + 1] = bottom.y;
vertexArray[offset + 2] = bottom.z;
vertexArray[offset + 3] = top.x;
vertexArray[offset + 4] = top.y;
vertexArray[offset + 5] = top.z;
vertexArray[offset + 6] = conebase.x;
vertexArray[offset + 7] = conebase.y;
vertexArray[offset + 8] = conebase.z;
if (i > 0) {
var prev_x = vertexArray[offset - 3];
var prev_y = vertexArray[offset - 2];
var prev_z = vertexArray[offset - 1];
var c = new Vector3(prev_x, prev_y, prev_z);
var b = new Vector3(end.x, end.y, end.z), b2 = to.clone();
var a = new Vector3(conebase.x, conebase.y, conebase.z);
shape.intersectionShape.triangle.push(new Triangle(a, b, c));
shape.intersectionShape.triangle.push(new Triangle(c.clone(), b2, a.clone()));
}
}
geoGroup.vertices += 48;
offset = geoGroup.vertices * 3;
// caps
vertexArray[offset] = from.x;
vertexArray[offset + 1] = from.y;
vertexArray[offset + 2] = from.z;
vertexArray[offset + 3] = to.x;
vertexArray[offset + 4] = to.y;
vertexArray[offset + 5] = to.z;
vertexArray[offset + 6] = end.x;
vertexArray[offset + 7] = end.y;
vertexArray[offset + 8] = end.z;
geoGroup.vertices += 3;
// now faces
var face, faceoffset, lineoffset;
var t1, t2, t2b, t3, t3b, t4, t1offset, t2offset, t2boffset, t3offset, t3boffset, t4offset;
var n1, n2, n3, n4;
var fromi = geoGroup.vertices - 3, toi = geoGroup.vertices - 2, endi = geoGroup.vertices - 1;
var fromoffset = fromi * 3, tooffset = toi * 3, endoffset = endi * 3;
for (i = 0, n = nvecs.length - 1; i < n; ++i) {
var ti = start + 3 * i;
offset = ti * 3;
faceoffset = geoGroup.faceidx;
lineoffset = geoGroup.lineidx;
t1 = ti;
t1offset = t1 * 3;
t2 = ti + 1;
t2offset = t2 * 3;
t2b = ti + 2;
t2boffset = t2b * 3;
t3 = ti + 4;
t3offset = t3 * 3;
t3b = ti + 5;
t3boffset = t3b * 3;
t4 = ti + 3;
t4offset = t4 * 3;
// face = [t1, t2, t4], [t2, t3, t4];
// face = [t1, t2, t3, t4];
// norm = [nvecs[i], nvecs[i], nvecs[i + 1], nvecs[i + 1]];
n1 = n2 = nvecs[i];
n3 = n4 = nvecs[i + 1];
normalArray[t1offset] = n1.x;
normalArray[t2offset] = n2.x;
normalArray[t4offset] = n4.x;
normalArray[t1offset + 1] = n1.y;
normalArray[t2offset + 1] = n2.y;
normalArray[t4offset + 1] = n4.y;
normalArray[t1offset + 2] = n1.z;
normalArray[t2offset + 2] = n2.z;
normalArray[t4offset + 2] = n4.z;
normalArray[t2offset] = n2.x;
normalArray[t3offset] = n3.x;
normalArray[t4offset] = n4.x;
normalArray[t2offset + 1] = n2.y;
normalArray[t3offset + 1] = n3.y;
normalArray[t4offset + 1] = n4.y;
normalArray[t2offset + 2] = n2.z;
normalArray[t3offset + 2] = n3.z;
normalArray[t4offset + 2] = n4.z;
normalArray[t2boffset] = n2.x;
normalArray[t3boffset] = n3.x;
normalArray[t2boffset + 1] = n2.y;
normalArray[t3boffset + 1] = n3.y;
normalArray[t2boffset + 2] = n2.z;
normalArray[t3boffset + 2] = n3.z;
// sides
faceArray[faceoffset] = t1;
faceArray[faceoffset + 1] = t2;
faceArray[faceoffset + 2] = t4;
faceArray[faceoffset + 3] = t2;
faceArray[faceoffset + 4] = t3;
faceArray[faceoffset + 5] = t4;
// caps
faceArray[faceoffset + 6] = t1;
faceArray[faceoffset + 7] = t4;
faceArray[faceoffset + 8] = fromi;
faceArray[faceoffset + 9] = t2b;
faceArray[faceoffset + 10] = toi;
faceArray[faceoffset + 11] = t3b;
// arrowhead
faceArray[faceoffset + 12] = t2b;
faceArray[faceoffset + 13] = endi;
faceArray[faceoffset + 14] = t3b;
// sides
lineArray[lineoffset] = t1;
lineArray[lineoffset + 1] = t2;
lineArray[lineoffset + 2] = t1;
lineArray[lineoffset + 3] = t4;
// lineArray[lineoffset+4] = t2, lineArray[lineoffset+5] = t3;
lineArray[lineoffset + 4] = t3;
lineArray[lineoffset + 5] = t4;
// caps
lineArray[lineoffset + 6] = t1;
lineArray[lineoffset + 7] = t4;
// lineArray[lineoffset+10] = t1, lineArray[lineoffset+11] = fromi;
// lineArray[lineoffset+12] = t4, lineArray[lineoffset+13] = fromi;
lineArray[lineoffset + 8] = t2b;
lineArray[lineoffset + 9] = t2; // toi
lineArray[lineoffset + 10] = t2b;
lineArray[lineoffset + 11] = t3b;
lineArray[lineoffset + 12] = t3;
lineArray[lineoffset + 13] = t3b; // toi
// arrowhead
lineArray[lineoffset + 14] = t2b;
lineArray[lineoffset + 15] = endi;
lineArray[lineoffset + 16] = t2b;
lineArray[lineoffset + 17] = t3b;
lineArray[lineoffset + 18] = endi;
lineArray[lineoffset + 19] = t3b;
geoGroup.faceidx += 15;
geoGroup.lineidx += 20;
}
// final face
face = [start + 45, start + 46, start + 1, start, start + 47,
start + 2];
//norm = [nvecs[15], nvecs[15], nvecs[0], nvecs[0]];
faceoffset = geoGroup.faceidx;
lineoffset = geoGroup.lineidx;
t1 = face[0];
t1offset = t1 * 3;
t2 = face[1];
t2offset = t2 * 3;
t2b = face[4];
t2boffset = t2b * 3;
t3 = face[2];
t3offset = t3 * 3;
t3b = face[5];
t3boffset = t3b * 3;
t4 = face[3];
t4offset = t4 * 3;
n1 = n2 = nvecs[15];
n3 = n4 = nvecs[0];
normalArray[t1offset] = n1.x;
normalArray[t2offset] = n2.x;
normalArray[t4offset] = n4.x;
normalArray[t1offset + 1] = n1.y;
normalArray[t2offset + 1] = n2.y;
normalArray[t4offset + 1] = n4.y;
normalArray[t1offset + 2] = n1.z;
normalArray[t2offset + 2] = n2.z;
normalArray[t4offset + 2] = n4.z;
normalArray[t2offset] = n2.x;
normalArray[t3offset] = n3.x;
normalArray[t4offset] = n4.x;
normalArray[t2offset + 1] = n2.y;
normalArray[t3offset + 1] = n3.y;
normalArray[t4offset + 1] = n4.y;
normalArray[t2offset + 2] = n2.z;
normalArray[t3offset + 2] = n3.z;
normalArray[t4offset + 2] = n4.z;
normalArray[t2boffset] = n2.x;
normalArray[t3boffset] = n3.x;
normalArray[t2boffset + 1] = n2.y;
normalArray[t3boffset + 1] = n3.y;
normalArray[t2boffset + 2] = n2.z;
normalArray[t3boffset + 2] = n3.z;
// Cap normals
dir.normalize();
negDir.normalize();
normalArray[fromoffset] = negDir.x;
normalArray[tooffset] = normalArray[endoffset] = dir.x;
normalArray[fromoffset + 1] = negDir.y;
normalArray[tooffset + 1] = normalArray[endoffset + 1] = dir.y;
normalArray[fromoffset + 2] = negDir.z;
normalArray[tooffset + 2] = normalArray[endoffset + 2] = dir.z;
// Final side
faceArray[faceoffset] = t1;
faceArray[faceoffset + 1] = t2;
faceArray[faceoffset + 2] = t4;
faceArray[faceoffset + 3] = t2;
faceArray[faceoffset + 4] = t3;
faceArray[faceoffset + 5] = t4;
// final caps
faceArray[faceoffset + 6] = t1;
faceArray[faceoffset + 7] = t4;
faceArray[faceoffset + 8] = fromi;
faceArray[faceoffset + 9] = t2b;
faceArray[faceoffset + 10] = toi;
faceArray[faceoffset + 11] = t3b;
// final arrowhead
faceArray[faceoffset + 12] = t2b;
faceArray[faceoffset + 13] = endi;
faceArray[faceoffset + 14] = t3b;
// sides
lineArray[lineoffset] = t1;
lineArray[lineoffset + 1] = t2;
lineArray[lineoffset + 2] = t1;
lineArray[lineoffset + 3] = t4;
// lineArray[lineoffset+4] = t2, lineArray[lineoffset+5] = t3;
lineArray[lineoffset + 4] = t3;
lineArray[lineoffset + 5] = t4;
// caps
lineArray[lineoffset + 6] = t1;
lineArray[lineoffset + 7] = t4;
// lineArray[lineoffset+10] = t1, lineArray[lineoffset+11] = fromi;
// lineArray[lineoffset+12] = t4, lineArray[lineoffset+13] = fromi;
lineArray[lineoffset + 8] = t2b;
lineArray[lineoffset + 9] = t2; // toi
lineArray[lineoffset + 10] = t2b;
lineArray[lineoffset + 11] = t3b;
lineArray[lineoffset + 12] = t3;
lineArray[lineoffset + 13] = t3b; // toi
// arrowhead
lineArray[lineoffset + 14] = t2b;
lineArray[lineoffset + 15] = endi;
lineArray[lineoffset + 16] = t2b;
lineArray[lineoffset + 17] = t3b;
lineArray[lineoffset + 18] = endi;
lineArray[lineoffset + 19] = t3b;
geoGroup.faceidx += 15;
geoGroup.lineidx += 20;
};
// Update a bounding sphere's position and radius
// from list of centroids and new points
/*
* @param {Sphere}
* sphere
* @param {Object}
* components, centroid of all objects in shape
* @param {Array}
* points, flat array of all points in shape
* @param {int} numPoints, number of valid poitns in points
*/
static updateBoundingFromPoints(sphere: Sphere, components, points, numPoints: number) {
sphere.center.set(0, 0, 0);
//previously I weighted each component's center equally, but I think
//it is better to use all points
let xmin = Infinity, ymin = Infinity, zmin = Infinity;
let xmax = -Infinity, ymax = -Infinity, zmax = -Infinity;
if (sphere.box) {
xmin = sphere.box.min.x;
xmax = sphere.box.max.x;
ymin = sphere.box.min.y;
ymax = sphere.box.max.y;
zmin = sphere.box.min.z;
zmax = sphere.box.max.z;
}
for (let i = 0, il = numPoints; i < il; i++) {
var x = points[i * 3], y = points[i * 3 + 1], z = points[i * 3 + 2];
if (x < xmin) xmin = x;
if (y < ymin) ymin = y;
if (z < zmin) zmin = z;
if (x > xmax) xmax = x;
if (y > ymax) ymax = y;
if (z > zmax) zmax = z;
}
sphere.center.set((xmax + xmin) / 2, (ymax + ymin) / 2, (zmax + zmin) / 2);
sphere.radius = sphere.center.distanceTo({ x: xmax, y: ymax, z: zmax });
sphere.box = { min: { x: xmin, y: ymin, z: zmin }, max: { x: xmax, y: ymax, z: zmax } };
};
//helper function for adding an appropriately sized mesh
private static addCustomGeo(shape: GLShape, geo: Geometry, mesh, color, clickable) {
var geoGroup = geo.addGeoGroup();
var vertexArr = mesh.vertexArr, normalArr = mesh.normalArr,
faceArr = mesh.faceArr;
geoGroup.vertices = vertexArr.length;
geoGroup.faceidx = faceArr.length;
var offset, v, a, b, c, i, il, r, g;
var vertexArray = geoGroup.vertexArray;
var colorArray = geoGroup.colorArray;
if (color.constructor !== Array) {
r = color.r;
g = color.g;
b = color.b;
}
for (i = 0, il = geoGroup.vertices; i < il; ++i) {
offset = i * 3;
v = vertexArr[i];
vertexArray[offset] = v.x;
vertexArray[offset + 1] = v.y;
vertexArray[offset + 2] = v.z;
if (color.constructor === Array) {
c = color[i];
r = c.r;
g = c.g;
b = c.b;
}
colorArray[offset] = r;
colorArray[offset + 1] = g;
colorArray[offset + 2] = b;
}
if (clickable) {
for (i = 0, il = geoGroup.faceidx / 3; i < il; ++i) {
offset = i * 3;
a = faceArr[offset];
b = faceArr[offset + 1];
c = faceArr[offset + 2];
var vA = new Vector3(), vB = new Vector3(), vC = new Vector3();
shape.intersectionShape.triangle.push(new Triangle(vA.copy(vertexArr[a]),
vB.copy(vertexArr[b]), vC.copy(vertexArr[c])));
}
}
if (clickable) {
var center = new Vector3(0, 0, 0);
var cnt = 0;
for (let g = 0; g < geo.geometryGroups.length; g++) {
center.add(geo.geometryGroups[g].getCentroid());
cnt++;
}
center.divideScalar(cnt);
GLShape.updateBoundingFromPoints(shape.boundingSphere, { centroid: center }, vertexArray, geoGroup.vertices);
}
geoGroup.faceArray = new Uint16Array(faceArr);
geoGroup.truncateArrayBuffers(true, true);
if (normalArr.length < geoGroup.vertices)
geoGroup.setNormals();
else {
var normalArray = geoGroup.normalArray = new Float32Array(geoGroup.vertices * 3);
var n;
for (i = 0, il = geoGroup.vertices; i < il; ++i) {
offset = i * 3;
n = normalArr[i];
normalArray[offset] = n.x;
normalArray[offset + 1] = n.y;
normalArray[offset + 2] = n.z;
}
}
geoGroup.setLineIndices();
geoGroup.lineidx = geoGroup.lineArray.length;
};
// handles custom shape generation from user supplied arrays
// May need to generate normal and/or line indices
/*
* @param {$3Dmol.GLShape}
* shape
* @param {geometry}
* geo
* @param {CustomShapeSpec}
* customSpec
*/
static drawCustom = function (shape: GLShape, geo: Geometry, customSpec: CustomShapeSpec) {
var mesh = customSpec;
var vertexArr = mesh.vertexArr;
var faceArr = mesh.faceArr;
if (vertexArr.length === 0 || faceArr.length === 0) {
console
.warn("Error adding custom shape component: No vertices and/or face indices supplied!");
}
var color = customSpec.color;
if (typeof (color) == 'undefined') {
color = shape.color;
}
color = CC.color(color);
//var firstgeo = geo.geometryGroups.length;
var splits = splitMesh(mesh);
for (var i = 0, n = splits.length; i < n; i++) {
GLShape.addCustomGeo(shape, geo, splits[i], splits[i].colorArr ? splits[i].colorArr : color, customSpec.clickable);
}
};
/*
*
* @param {$3Dmol.GLShape}
* shape
* @param {ShapeSpec}
* stylespec
* @returns {undefined}
*/
static updateFromStyle(shape: GLShape, stylespec: ShapeSpec) {
if (typeof (stylespec.color) != 'undefined') {
shape.color = stylespec.color || new Color();
if (!(stylespec.color instanceof Color))
shape.color = CC.color(stylespec.color);
} else {
shape.color = CC.color(0);
}
shape.wireframe = stylespec.wireframe ? true : false;
//opacity is the preferred nomenclature, support alpha for backwards compat
shape.opacity = stylespec.alpha ? clamp(stylespec.alpha, 0.0,
1.0) : 1.0;
if (typeof (stylespec.opacity) != 'undefined') {
shape.opacity = clamp(stylespec.opacity, 0.0, 1.0);
}
shape.side = (stylespec.side !== undefined) ? stylespec.side : DoubleSide;
shape.linewidth = typeof (stylespec.linewidth) == 'undefined' ? 1 : stylespec.linewidth;
// Click handling
shape.clickable = stylespec.clickable ? true : false;
shape.callback = makeFunction(stylespec.callback);
shape.hoverable = stylespec.hoverable ? true : false;
shape.hover_callback = makeFunction(stylespec.hover_callback);
shape.unhover_callback = makeFunction(stylespec.unhover_callback);
shape.contextMenuEnabled = !!stylespec.contextMenuEnabled;
shape.hidden = stylespec.hidden;
shape.frame = stylespec.frame;
};
boundingSphere: Sphere;
intersectionShape: any;
color: any = 0xffffff;
hidden = false;
wireframe = false;
opacity = 1;
linewidth = 1;
clickable = false;
callback: Func;
hoverable = false;
hover_callback: Func;
unhover_callback: Func;
contextMenuEnabled: boolean = false;
frame: any;
side = DoubleSide;
shapePosition: any;
private geo: Geometry;
private linegeo: Geometry;
private stylespec: any;
private components: any;
private shapeObj: any;
private renderedShapeObj: any;
/**
* Custom renderable shape
*
* @constructor
*
* @param {ShapeSpec} stylespec
*/
constructor(stylespec: ShapeSpec) {
this.stylespec = stylespec || {};
this.boundingSphere = new Sphere();
/** @type {IntersectionShapes} */
this.intersectionShape = {
sphere: [],
cylinder: [],
line: [],
triangle: []
};
GLShape.updateFromStyle(this, this.stylespec);
// Keep track of shape components and their centroids
this.components = [];
this.shapeObj = null;
this.renderedShapeObj = null;
this.geo = new Geometry(true);
this.linegeo = new Geometry(true);
};
/** Update shape with new style specification
* @param {ShapeSpec} newspec
@example
let sphere = viewer.addSphere({center:{x:0,y:0,z:0},radius:10.0,color:'red'});
sphere.updateStyle({color:'yellow',opacity:0.5});
viewer.render();
*/
updateStyle(newspec: ShapeSpec) {
for (var prop in newspec) {
this.stylespec[prop] = newspec[prop];
}
GLShape.updateFromStyle(this, this.stylespec);
if (newspec.voldata && newspec.volscheme) {
adjustVolumeStyle(newspec);
//convert volumetric data into colors
const scheme = newspec.volscheme;
const voldata = newspec.voldata;
const cc = CC;
const range = scheme.range() || [-1, 1];
this.geo.setColors(function (x, y, z) {
let val = voldata.getVal(x, y, z);
let col = cc.color(scheme.valueToHex(val, range));
return col;
});
delete this.color;
}
};
/**
* Creates a custom shape from supplied vertex and face arrays
* @param {CustomShapeSpec} customSpec
*/
public addCustom(customSpec: CustomShapeSpec) {
customSpec.vertexArr = customSpec.vertexArr || [];
customSpec.faceArr = customSpec.faceArr || [];
customSpec.normalArr = customSpec.normalArr || [];
// will split mesh as needed
GLShape.drawCustom(this, this.geo, customSpec);
};
/**
* Creates a sphere shape
* @param {SphereSpec} sphereSpec
@example
viewer.addSphere({center:{x:0,y:0,z:0},radius:10.0,color:'red'});
viewer.render();
*/
public addSphere(sphereSpec: SphereSpec) {
if (!sphereSpec.center) {
sphereSpec.center = new Vector3(0, 0, 0);
}
sphereSpec.radius = sphereSpec.radius ? clamp(sphereSpec.radius, 0, Infinity) : 1.5;
sphereSpec.color = CC.color(sphereSpec.color);
this.intersectionShape.sphere.push(new Sphere(sphereSpec.center, sphereSpec.radius));
GLDraw.drawSphere(this.geo, sphereSpec.center,
sphereSpec.radius, sphereSpec.color as Colored, sphereSpec.quality);
this.components.push({
centroid: new Vector3(sphereSpec.center.x,
sphereSpec.center.y, sphereSpec.center.z)
});
var geoGroup = this.geo.updateGeoGroup(0);
GLShape.updateBoundingFromPoints(this.boundingSphere, this.components,
geoGroup.vertexArray, geoGroup.vertices);
};
/**
* Creates a box
* @param {BoxSpec} boxSpec
@example
var shape = viewer.addShape({color:'red'});
shape.addBox({corner: {x:1,y:2,z:0}, dimensions: {w: 4, h: 2, d: 6}});
shape.addBox({corner: {x:-5,y:-3,z:0},
dimensions: { w: {x:1,y:1,z:0},
h: {x:-1,y:1,z:0},
d: {x:0,y:0,z:1} }});
viewer.zoomTo();
viewer.rotate(30);
viewer.render();
*/
public addBox(boxSpec: BoxSpec) {
var dim = boxSpec.dimensions || { w: 1, h: 1, d: 1 };
//dimensions may be scalar or vector quantities
var w: XYZ;
if (typeof (dim.w) == "number") {
w = { x: dim.w, y: 0, z: 0 };
} else {
w = dim.w;
}
var h: XYZ;
if (typeof (dim.h) == "number") {
h = { x: 0, y: dim.h, z: 0 };
} else {
h = dim.h;
}
var d: XYZ;
if (typeof (dim.d) == "number") {
d = { x: 0, y: 0, z: dim.d };
} else {
d = dim.d;
}
//can position using corner OR center
var c = boxSpec.corner;
if (c == undefined) {
if (boxSpec.center !== undefined) {
c = {
x: boxSpec.center.x - 0.5 * (w.x + h.x + d.x),
y: boxSpec.center.y - 0.5 * (w.y + h.y + d.y),
z: boxSpec.center.z - 0.5 * (w.z + h.z + d.z)
};
} else { // default to origin
c = { x: 0, y: 0, z: 0 };
}
}
//8 vertices
var uv =
[{ x: c.x, y: c.y, z: c.z },
{ x: c.x + w.x, y: c.y + w.y, z: c.z + w.z },
{ x: c.x + h.x, y: c.y + h.y, z: c.z + h.z },
{ x: c.x + w.x + h.x, y: c.y + w.y + h.y, z: c.z + w.z + h.z },
{ x: c.x + d.x, y: c.y + d.y, z: c.z + d.z },
{ x: c.x + w.x + d.x, y: c.y + w.y + d.y, z: c.z + w.z + d.z },
{ x: c.x + h.x + d.x, y: c.y + h.y + d.y, z: c.z + h.z + d.z },
{ x: c.x + w.x + h.x + d.x, y: c.y + w.y + h.y + d.y, z: c.z + w.z + h.z + d.z }];
//but.. so that we can have sharp issues, we want a unique normal
//for each face - since normals are associated with vertices, need to duplicate
//bottom
// 0 1
// 2 3
//top
// 4 5
// 6 7
var verts = [];
var faces = [];
//bottom
verts.splice(verts.length, 0, uv[0], uv[1], uv[2], uv[3]);
faces.splice(faces.length, 0, 0, 2, 1, 1, 2, 3);
var foff = 4;
//front
verts.splice(verts.length, 0, uv[2], uv[3], uv[6], uv[7]);
faces.splice(faces.length, 0, foff + 0, foff + 2, foff + 1, foff + 1, foff + 2, foff + 3);
foff += 4;
//back
verts.splice(verts.length, 0, uv[4], uv[5], uv[0], uv[1]);
faces.splice(faces.length, 0, foff + 0, foff + 2, foff + 1, foff + 1, foff + 2, foff + 3);
foff += 4;
//top
verts.splice(verts.length, 0, uv[6], uv[7], uv[4], uv[5]);
faces.splice(faces.length, 0, foff + 0, foff + 2, foff + 1, foff + 1, foff + 2, foff + 3);
foff += 4;
//right
verts.splice(verts.length, 0, uv[3], uv[1], uv[7], uv[5]);
faces.splice(faces.length, 0, foff + 0, foff + 2, foff + 1, foff + 1, foff + 2, foff + 3);
foff += 4;
//left
verts.splice(verts.length, 0, uv[2], uv[6], uv[0], uv[4]); // fix: was 2 0 6 4 , was flipped! will this ruin anything?
// and is this the reason for having double sided lambert shading? the box had a flipped face
faces.splice(faces.length, 0, foff + 0, foff + 2, foff + 1, foff + 1, foff + 2, foff + 3);
foff += 4;
var spec = extend({}, boxSpec);
spec.vertexArr = verts;
spec.faceArr = faces;
spec.normalArr = [];
GLShape.drawCustom(this, this.geo, spec);
var centroid = new Vector3();
this.components.push({
centroid: centroid.addVectors(uv[0], uv[7]).multiplyScalar(0.5)
});
var geoGroup = this.geo.updateGeoGroup(0);
GLShape.updateBoundingFromPoints(this.boundingSphere, this.components, geoGroup.vertexArray, geoGroup.vertices);
};
/**
* Creates a cylinder shape
* @param {CylinderSpec} cylinderSpec
@example
viewer.addCylinder({start:{x:0.0,y:0.0,z:0.0},
end:{x:10.0,y:0.0,z:0.0},
radius:1.0,
fromCap:1,
toCap:2,
color:'red',
hoverable:true,
clickable:true,
callback:function(){ this.color.setHex(0x00FFFF00);viewer.render( );},
hover_callback: function(){ viewer.render( );},
unhover_callback: function(){ this.color.setHex(0xFF000000);viewer.render( );}
});
viewer.addCylinder({start:{x:0.0,y:2.0,z:0.0},
end:{x:0.0,y:10.0,z:0.0},
radius:0.5,
fromCap:false,
toCap:true,
color:'teal'});
viewer.addCylinder({start:{x:15.0,y:0.0,z:0.0},
end:{x:20.0,y:0.0,z:0.0},
radius:1.0,
color:'black',
fromCap:false,
toCap:false});
viewer.render();
*/
public addCylinder(cylinderSpec: CylinderSpec) {
var start: Vector3;
var end: Vector3;
if (!cylinderSpec.start) {
start = new Vector3(0, 0, 0);
} else {
start = new Vector3(cylinderSpec.start.x || 0,
cylinderSpec.start.y || 0, cylinderSpec.start.z || 0);
}
if (!cylinderSpec.end) {
end = new Vector3(0, 0, 0);
} else {
end = new Vector3(cylinderSpec.end.x,
cylinderSpec.end.y || 0, cylinderSpec.end.z || 0);
if (typeof (end.x) == 'undefined') end.x = 3; //show something even if undefined
}
var radius = cylinderSpec.radius || 0.1;
var color = CC.color(cylinderSpec.color);
this.intersectionShape.cylinder.push(new Cylinder(start, end, radius));
GLDraw.drawCylinder(this.geo, start, end, radius, color, cylinderSpec.fromCap, cylinderSpec.toCap);
var centroid = new Vector3();
this.components.push({
centroid: centroid.addVectors(start, end).multiplyScalar(0.5)
});
var geoGroup = this.geo.updateGeoGroup(0);
GLShape.updateBoundingFromPoints(this.boundingSphere, this.components,
geoGroup.vertexArray, geoGroup.vertices);
};
/**
* Creates a dashed cylinder shape
* @param {CylinderSpec} cylinderSpec
*/
public addDashedCylinder(cylinderSpec: CylinderSpec) {
cylinderSpec.dashLength = cylinderSpec.dashLength || 0.25;
cylinderSpec.gapLength = cylinderSpec.gapLength || 0.25;
var start: Vector3;
if (!cylinderSpec.start) start = new Vector3(0, 0, 0);
else {
start = new Vector3(cylinderSpec.start.x || 0,
cylinderSpec.start.y || 0, cylinderSpec.start.z || 0);
}
var end: Vector3;
if (!cylinderSpec.end) end = new Vector3(3, 0, 0);
else {
end = new Vector3(cylinderSpec.end.x,
cylinderSpec.end.y || 0, cylinderSpec.end.z || 0);
if (typeof (end.x) == 'undefined') end.x = 3; //show something even if undefined
}
var radius = cylinderSpec.radius || 0.1;
var color = CC.color(cylinderSpec.color);
var cylinderLength = Math.sqrt(Math.pow((start.x - end.x), 2) + Math.pow((start.y - end.y), 2) + Math.pow((start.z - end.z), 2));
var count = cylinderLength / (cylinderSpec.gapLength + cylinderSpec.dashLength);
var new_start = new Vector3(cylinderSpec.start.x || 0,
cylinderSpec.start.y || 0, cylinderSpec.start.z || 0);
var new_end = new Vector3(cylinderSpec.end.x,
cylinderSpec.end.y || 0, cylinderSpec.end.z || 0);
var gapVector = new Vector3((end.x - start.x) / (cylinderLength / cylinderSpec.gapLength), (end.y - start.y) / (cylinderLength / cylinderSpec.gapLength), (end.z - start.z) / (cylinderLength / cylinderSpec.gapLength));
var dashVector = new Vector3((end.x - start.x) / (cylinderLength / cylinderSpec.dashLength), (end.y - start.y) / (cylinderLength / cylinderSpec.dashLength), (end.z - start.z) / (cylinderLength / cylinderSpec.dashLength));
for (var place = 0; place < count; place++) {
new_end = new Vector3(new_start.x + dashVector.x, new_start.y + dashVector.y, new_start.z + dashVector.z);
this.intersectionShape.cylinder.push(new Cylinder(new_start, new_end, radius));
GLDraw.drawCylinder(this.geo, new_start, new_end, radius, color, cylinderSpec.fromCap, cylinderSpec.toCap);
new_start = new Vector3(new_end.x + gapVector.x, new_end.y + gapVector.y, new_end.z + gapVector.z);
}
var centroid = new Vector3();
this.components.push({
centroid: centroid.addVectors(start, end).multiplyScalar(0.5)
});
var geoGroup = this.geo.updateGeoGroup(0);
GLShape.updateBoundingFromPoints(this.boundingSphere, this.components,
geoGroup.vertexArray, geoGroup.vertices);
};
/**
* Creates a curved shape
* @param {CurveSpec} curveSpec
*/
public addCurve(curveSpec: CurveSpec) {
curveSpec.points = curveSpec.points || [];
curveSpec.smooth = curveSpec.smooth || 10;
if (typeof (curveSpec.fromCap) == "undefined") curveSpec.fromCap = 2;
if (typeof (curveSpec.toCap) == "undefined") curveSpec.toCap = 2;
//subdivide into smoothed spline points
var points = subdivide_spline(curveSpec.points, curveSpec.smooth);
if (points.length < 3) {
console.log("Too few points in addCurve");
return;
}
var radius = curveSpec.radius || 0.1;
var color = CC.color(curveSpec.color);
//TODO TODO - this is very inefficient, should create our
//own water tight model with proper normals...
//if arrows are requested, peel off enough points to fit
//at least 2*r of arrowness
var start = 0;
var end = points.length - 1;
var segmentlen = points[0].distanceTo(points[1]);
var npts = Math.ceil(2 * radius / segmentlen);
if (curveSpec.toArrow) {
end -= npts;
let arrowspec = {
start: points[end],
end: points[points.length - 1],
radius: radius,
color: color as ColorSpec,
mid: 0.0001
};
this.addArrow(arrowspec);
}
if (curveSpec.fromArrow) {
start += npts;
let arrowspec = {
start: points[start],
end: points[0],
radius: radius,
color: color as ColorSpec,
mid: 0.0001
};
this.addArrow(arrowspec);
}
var midway = Math.ceil(points.length / 2);
var middleSpec: any = { radius: radius, color: color, fromCap: 2, toCap: 2 };
for (var i = start; i < end; i++) {
middleSpec.start = points[i];
middleSpec.end = points[i + 1];
middleSpec.fromCap = 2;
middleSpec.toCap = 2;
if (i < midway) {
middleSpec.fromCap = 2;
middleSpec.toCap = 0;
} else if (i > midway) {
middleSpec.fromCap = 0;
middleSpec.toCap = 2;
} else {
middleSpec.fromCap = 2;
middleSpec.toCap = 2;
}
this.addCylinder(middleSpec);
}
};
/**
* Creates a line shape
* @param {LineSpec} lineSpec
@example
$3Dmol.download("pdb:2ABJ",viewer,{},function(){
viewer.addLine({dashed:true,start:{x:0,y:0,z:0},end:{x:100,y:100,z:100}});
viewer.render(callback);
});
*/
public addLine(lineSpec: LineSpec) {
var start: Vector3;
var end: Vector3;
if (!lineSpec.start) {
start = new Vector3(0, 0, 0);
} else {
start = new Vector3(lineSpec.start.x || 0,
lineSpec.start.y || 0, lineSpec.start.z || 0);
}
if (!lineSpec.end) {
end = new Vector3(3, 0, 0);
} else {
end = new Vector3(lineSpec.end.x,
lineSpec.end.y || 0, lineSpec.end.z || 0);
if (typeof (end.x) == 'undefined') end.x = 3; //show something even if undefined
}
var geoGroup = this.geo.updateGeoGroup(2);
//make line from start to end
//for consistency with rest of shapes, uses vertices and lines rather
//than a separate line geometry
var vstart = geoGroup.vertices;
var i = vstart * 3;
var vertexArray = geoGroup.vertexArray;
vertexArray[i] = start.x;
vertexArray[i + 1] = start.y;
vertexArray[i + 2] = start.z;
vertexArray[i + 3] = end.x;
vertexArray[i + 4] = end.y;
vertexArray[i + 5] = end.z;
geoGroup.vertices += 2;
var lineArray = geoGroup.lineArray;
var li = geoGroup.lineidx;
lineArray[li] = vstart;
lineArray[li + 1] = vstart + 1;
geoGroup.lineidx += 2;
var centroid = new Vector3();
this.components.push({
centroid: centroid.addVectors(start, end).multiplyScalar(0.5)
});
geoGroup = this.geo.updateGeoGroup(0);
GLShape.updateBoundingFromPoints(this.boundingSphere, this.components,
geoGroup.vertexArray, geoGroup.vertices);
};
/**
* Creates an arrow shape
* @param {ArrowSpec} arrowSpec
@example
$3Dmol.download("pdb:4DM7",viewer,{},function(){
viewer.setBackgroundColor(0xffffffff);
viewer.addArrow({
start: {x:-10.0, y:0.0, z:0.0},
end: {x:0.0, y:-10.0, z:0.0},
radius: 1.0,
radiusRadio:1.0,
mid:1.0,
clickable:true,
callback:function(){
this.color.setHex(0xFF0000FF);
viewer.render( );
}
});
viewer.render();
});
*/
public addArrow(arrowSpec: ArrowSpec) {
if (!arrowSpec.start) {
arrowSpec.start = new Vector3(0, 0, 0);
} else {
arrowSpec.start = new Vector3(arrowSpec.start.x || 0,
arrowSpec.start.y || 0, arrowSpec.start.z || 0);
}
if (arrowSpec.dir instanceof Vector3 && typeof (arrowSpec.length) === 'number') {
var end = arrowSpec.dir.clone().multiplyScalar(arrowSpec.length).add(
arrowSpec.start);
arrowSpec.end = end;
}
else if (!arrowSpec.end) {
arrowSpec.end = new Vector3(3, 0, 0);
} else {
arrowSpec.end = new Vector3(arrowSpec.end.x,
arrowSpec.end.y || 0, arrowSpec.end.z || 0);
if (typeof (arrowSpec.end.x) == 'undefined') arrowSpec.end.x = 3; //show something even if undefined
}
arrowSpec.radius = arrowSpec.radius || 0.1;
arrowSpec.radiusRatio = arrowSpec.radiusRatio || 1.618034;
arrowSpec.mid = (0 < arrowSpec.mid && arrowSpec.mid < 1) ? arrowSpec.mid
: 0.618034;
GLShape.drawArrow(this, this.geo, arrowSpec);
var centroid = new Vector3();
this.components.push({
centroid: centroid.addVectors(arrowSpec.start, arrowSpec.end)
.multiplyScalar(0.5)
});
var geoGroup = this.geo.updateGeoGroup(0);
GLShape.updateBoundingFromPoints(this.boundingSphere, this.components,
geoGroup.vertexArray, geoGroup.vertices);
};
static distance_from(c1: XYZ, c2: XYZ) {
return Math.sqrt(Math.pow((c1.x - c2.x), 2) + Math.pow((c1.y - c2.y), 2) + Math.pow((c1.z - c2.z), 2));
};
static inSelectedRegion(coordinate: XYZ, selectedRegion, radius: number) {
for (var i = 0; i < selectedRegion.length; i++) {
if (GLShape.distance_from(selectedRegion[i], coordinate) <= radius)
return true;
}
return false;
};
/**
* Create isosurface from volumetric data.
* @param {VolumeData} data - volumetric input data
* @param {IsoSurfaceSpec} isoSpec - volumetric data shape specification
* @example //the user can specify a selected region for the isosurface
$.get('../test_structs/benzene-homo.cube', function(data){
var voldata = new $3Dmol.VolumeData(data, "cube");
viewer.addIsosurface(voldata, {isoval: 0.01,
color: "blue",
alpha: 0.5,
smoothness: 10});
viewer.addIsosurface(voldata, {isoval: -0.01,
color: "red",
smoothness: 5,
opacity:0.5,
wireframe:true,
clickable:true,
callback:
function() {
this.opacity = 0.0;
viewer.render( );
}});
viewer.setStyle({}, {stick:{}});
viewer.zoomTo();
viewer.render();
});
*/
addIsosurface(data, volSpec:IsoSurfaceSpec, callback?, viewer?: GLViewer) {//may want to cache the arrays generated when selectedRegion ==true
var isoval = (volSpec.isoval !== undefined && typeof (volSpec.isoval) === "number") ? volSpec.isoval
: 0.0;
var voxel = (volSpec.voxel) ? true : false;
var smoothness = (volSpec.smoothness === undefined) ? 1 : volSpec.smoothness;
var nX = data.size.x;
var nY = data.size.y;
var nZ = data.size.z;
var vertnums = new Int16Array(nX * nY * nZ);
var vals = data.data;
var i, il;
for (i = 0, il = vertnums.length; i < il; ++i)
vertnums[i] = -1;
var bitdata = new Uint8Array(nX * nY * nZ);
//mark locations partitioned by isoval
for (i = 0, il = vals.length; i < il; ++i) {
var val = (isoval >= 0) ? vals[i] - isoval : isoval - vals[i];
if (val > 0)
bitdata[i] |= GLShape.ISDONE;
}
var verts = [], faces = [];
MarchingCube.march(bitdata, verts, faces, {
fulltable: true,
voxel: voxel,
unitCube: data.unit,
origin: data.origin,
matrix: data.matrix,
nX: nX,
nY: nY,
nZ: nZ
});
if (!voxel && smoothness > 0)
MarchingCube.laplacianSmooth(smoothness, verts, faces);
var vertexmapping = [];
var newvertices = [];
var newfaces = [];
if (volSpec.selectedRegion && volSpec.coords === undefined) {
volSpec.coords = volSpec.selectedRegion; //backwards compat for incorrectly documented feature
}
if (volSpec.coords === undefined && volSpec.selection !== undefined) {
if(!viewer) {
console.log("addIsosurface needs viewer is selection provided.");
} else {
volSpec.coords = viewer.selectedAtoms(volSpec.selection) as XYZ[];
}
}
if (volSpec.coords !== undefined) {
var xmax = volSpec.coords[0].x,
ymax = volSpec.coords[0].y,
zmax = volSpec.coords[0].z,
xmin = volSpec.coords[0].x,
ymin = volSpec.coords[0].y,
zmin = volSpec.coords[0].z;
for (let i = 0; i < volSpec.coords.length; i++) {
if (volSpec.coords[i].x > xmax)
xmax = volSpec.coords[i].x;
else if (volSpec.coords[i].x < xmin)
xmin = volSpec.coords[i].x;
if (volSpec.coords[i].y > ymax)
ymax = volSpec.coords[i].y;
else if (volSpec.coords[i].y < ymin)
ymin = volSpec.coords[i].y;
if (volSpec.coords[i].z > zmax)
zmax = volSpec.coords[i].z;
else if (volSpec.coords[i].z < zmin)
zmin = volSpec.coords[i].z;
}
var rad = 2;
if (volSpec.radius !== undefined) {
rad = volSpec.radius; //backwards compat
}
if (volSpec.selectedOffset !== undefined) { //backwards compat
rad = volSpec.selectedOffset;
}
if (volSpec.seldist !== undefined) {
rad = volSpec.seldist;
}
xmin -= rad;
xmax += rad;
ymin -= rad;
ymax += rad;
zmin -= rad;
zmax += rad;
// accounts for radius
for (let i = 0; i < verts.length; i++) {
if (verts[i].x > xmin &&
verts[i].x < xmax &&
verts[i].y > ymin &&
verts[i].y < ymax &&
verts[i].z > zmin &&
verts[i].z < zmax &&
GLShape.inSelectedRegion(verts[i],
volSpec.coords, rad)) {
vertexmapping.push(newvertices.length);
newvertices.push(verts[i]);
} else {
vertexmapping.push(-1);
}
}
for (let i = 0; i + 2 < faces.length; i += 3) {
if (vertexmapping[faces[i]] !== -1 &&
vertexmapping[faces[i + 1]] !== -1 &&
vertexmapping[faces[i + 2]] !== -1) {
newfaces.push(faces[i] - (faces[i] - vertexmapping[faces[i]]));
newfaces.push(faces[i + 1] - (faces[i + 1] - vertexmapping[faces[i + 1]]));
newfaces.push(faces[i + 2] - (faces[i + 2] - vertexmapping[faces[i + 2]]));
}
}
verts = newvertices;
faces = newfaces;
}
GLShape.drawCustom(this, this.geo, {
vertexArr: verts,
faceArr: faces,
normalArr: [],
clickable: volSpec.clickable,
hoverable: volSpec.hoverable
});
this.updateStyle(volSpec);
//computing bounding sphere from vertices
var origin = new Vector3(data.origin.x, data.origin.y, data.origin.z);
var size = new Vector3(data.size.x * data.unit.x, data.size.y * data.unit.y, data.size.z * data.unit.z);
var total = new Vector3(0, 0, 0);
var maxv = origin.clone();
var minv = origin.clone().add(size);
for (let i = 0; i < verts.length; i++) {
total.add(verts[i]);
maxv.max(verts[i]);
minv.min(verts[i]);
}
total.divideScalar(verts.length);
var len1 = total.distanceTo(minv);
var len2 = total.distanceTo(maxv);
this.boundingSphere.center = total;
this.boundingSphere.radius = Math.max(len1, len2);
if (typeof callback == "function")
callback();
};
/**
* @deprecated Use addIsosurface instead
* Creates custom shape from volumetric data
* @param {string} data - Volumetric input data
* @param {string} fmt - Input data format (e.g. 'cube' for cube file format)
* @param {IsoSurfaceSpec} isoSpec - Volumetric data shape specification
*/
public addVolumetricData(data, fmt, volSpec: IsoSurfaceSpec) {
data = new VolumeData(data, fmt);
this.addIsosurface(data, volSpec);
};
//for internal use, truncate buffers to save memory
finalize() {
GLShape.finalizeGeo(this.geo);
this.geo.initTypedArrays();
return this.geo;
};
/*
* Initialize webgl objects for rendering
* @param {$3Dmol.Object3D} group
*
*/
globj(group) {
if (this.renderedShapeObj) {
group.remove(this.renderedShapeObj);
this.renderedShapeObj = null;
}
if (this.hidden)
return;
GLShape.finalizeGeo(this.geo);
this.geo.initTypedArrays();
if (this.wireframe) {
this.geo.setUpWireframe();
}
if (typeof (this.color) != 'undefined')
GLShape.updateColor(this.geo, this.color);
this.shapeObj = new Object3D();
var material = null;
if (this.side == DoubleSide) {
material = new MeshDoubleLambertMaterial({
wireframe: this.wireframe,
side: this.side,
transparent: (this.opacity < 1) ? true : false,
opacity: this.opacity,
wireframeLinewidth: this.linewidth,
vertexColors: Coloring.VertexColors
});
} else {
material = new MeshLambertMaterial({
wireframe: this.wireframe,
side: this.side,
transparent: (this.opacity < 1) ? true : false,
opacity: this.opacity,
wireframeLinewidth: this.linewidth,
vertexColors: Coloring.VertexColors
});
}
var mesh = new Mesh(this.geo, material);
this.shapeObj.add(mesh);
if(this.linegeo && this.linegeo.vertices > 0) {
var lineMaterial = new LineBasicMaterial({
linewidth: this.linewidth,
color: this.color
});
var line = new Line(this.linegeo, lineMaterial as Material, LineStyle.LinePieces);
this.shapeObj.add(line);
}
this.renderedShapeObj = this.shapeObj.clone();
group.add(this.renderedShapeObj);
};
removegl(group) {
if (this.renderedShapeObj) {
// dispose of geos and materials
if (this.renderedShapeObj.geometry !== undefined)
this.renderedShapeObj.geometry.dispose();
if (this.renderedShapeObj.material !== undefined)
this.renderedShapeObj.material.dispose();
group.remove(this.renderedShapeObj);
this.renderedShapeObj = null;
}
this.shapeObj = null;
};
get position() {
return this.boundingSphere.center;
}
get x() {
return this.boundingSphere.center.x;
}
get y() {
return this.boundingSphere.center.y;
}
get z() {
return this.boundingSphere.center.z;
}
};
export function splitMesh(mesh) {
var MAXVERT = 64000; //webgl only supports 2^16 elements, leave a little breathing room (require at least 2)
//peel off 64k vertices rsvh into their own mesh
//duplicating vertices and normals as necessary to preserve faces and lines
if (mesh.vertexArr.length < MAXVERT) return [mesh]; //typical case
var slices: any = [{ vertexArr: [], normalArr: [], faceArr: [] }];
if (mesh.colorArr) slices.colorArr = [];
var vertSlice = []; //indexed by original vertex to get current slice
var vertIndex = []; //indexed by original vertex to get index within slice
var currentSlice = 0;
//for each face, make sure all three vertices (or copies) are in the same slice
var faces = mesh.faceArr;
for (let i = 0, nf = faces.length; i < nf; i += 3) {
let slice = slices[currentSlice];
for (let j = 0; j < 3; j++) {
//process each vertex to make sure it is assigned a slice
//all vertices of a face must belong to the same slice
var v = faces[i + j];
if (vertSlice[v] !== currentSlice) { //true if undefined
vertSlice[v] = currentSlice;
vertIndex[v] = slice.vertexArr.length;
slice.vertexArr.push(mesh.vertexArr[v]);
if (mesh.normalArr && mesh.normalArr[v]) slice.normalArr.push(mesh.normalArr[v]);
if (mesh.colorArr && mesh.colorArr[v]) slice.colorArr.push(mesh.colorArr[v]);
}
slice.faceArr.push(vertIndex[v]);
}
if (slice.vertexArr.length >= MAXVERT) {
//new slice
slices.push({ vertexArr: [], normalArr: [], faceArr: [] });
if (mesh.colorArr) slices.colorArr = [];
currentSlice++;
}
}
return slices;
};
/**
* GLShape style specification
*/
export interface ShapeSpec {
/** Either a single color for the whole object or an array specifying the color at each vertex ({@link CustomShapeSpec}). */
color?: ColorSpec | ColorSpec[];
alpha?: number; //prefer opacity
/** transparency, between 0 (invisible) and 1 (opaque) */
opacity?: number;
/** draw as wireframe, not surface */
wireframe?: boolean;
/** if true, do not display object */
hidden?: boolean;
/** width of line for wireframe rendering **No longer supported by most browsers** */
linewidth?: number;
/** if true, user can click on object to trigger callback */
clickable?: boolean;
/** function to call on click */
callback?: Func;
/** if true, user can hover on object to trigger callback */
hoverable?: boolean;
/** hover callback */
hover_callback?: Func;
/** unhover callback */
unhover_callback?: Func;
/** if true, user can right-click or long press to trigger callback */
contextMenuEnabled?: boolean;
/** if set, only display in this frame of an animation */
frame?: number;
side?: number;
voldata?: VolumeData;
volscheme?: GradientType
};
/**
* Isosurface style specification
* @extends ShapeSpec
*/
export interface IsoSurfaceSpec extends ShapeSpec {
/** specifies the isovalue to draw surface at */
isoval?: number;
/** if true uses voxel style rendering */
voxel?: boolean;
/** amount to smooth surface (default 1) */
smoothness?: number;
/** coordinates around which to include data; use viewer.selectedAtoms() to convert an AtomSelectionSpec to coordinates */
coords?: XYZ[];
/** selection around which to include data (alternative to coords) */
selection?: AtomSelectionSpec;
/** distance around coords to include data [default = 2.0] */
seldist?: number;
/** volumetric data for vertex coloring, can be VolumeData object or raw data if volformat is specified */
voldata?: VolumeData;
/** coloring scheme for mapping volumetric data to vertex color, if not a Gradient object, show describe a builtin gradient one by providing an object with gradient, min, max, and (optionally) mid fields. */
volscheme?: GradientType;
/** format of voldata if not a $3Dmol.VolumeData object */
volformat?: string;
selectedRegion?: XYZ[]; //deprecated
selectedOffset?: number; //deprecated
radius?: number; //also deprecated
};
/**
* Arrow shape specification.
* @extends ShapeSpec
*/
export interface ArrowSpec extends ShapeSpec {
/** starting position */
start?: XYZ;
/** ending position */
end?: XYZ;
/** direction to extend from start (instead of specifying end) */
dir?: XYZ;
/** length to extend in dir direction from start (instead of specifying end) */
length?: number;
/** radius (default 0.1A) */
radius?: number;
/** color */
color?: ColorSpec;
/** hidden */
hidden?: boolean;
/** ratio of arrow base to cylinder (1.618034 default) */
radiusRatio?: number;
/** relative position of arrow base (0.618034 default) */
mid?: number;
/** position of arrow base in length units, if negative positioned from end instead of start. Overrides mid. */
midpos?: number;
};
/**
* Cylinder shape specification.
* @extends ShapeSpec
*
*/
export interface CylinderSpec extends ShapeSpec {
/** starting vector */
start?: XYZ;
/** ending position */
end?: XYZ;
/** radius */
radius?: number;
/** Place a cap at the start (none, flat or round) */
fromCap?: CAP | string;
/** Place a cap at the end (none, flat or round) */
toCap?: CAP | string;
/** Make the cylinder dashed. */
dashed?: boolean;
/** Length of dashes (default 0.25) */
dashLength?: number;
/** Length of gaps (default 0.25) */
gapLength?: number;
};
/**
* Curve shape specification.
* @extends ShapeSpec
*/
export interface CurveSpec extends ShapeSpec {
/** Sequence of points to draw curve through */
points?: XYZ[];
/** amount of interpolation */
smooth?: number;
/** radius of curve */
radius?: number;
/** if an arrow should be drawn at the start */
fromArrow?: boolean;
/** if an arrow should be drawn at the end */
toArrow?: boolean;
/** have cap at start */
fromCap?: CAP;
/** have cap at end */
toCap?: CAP;
};
/**
* Line shape specification. Default to wireframe.
* @extends ShapeSpec
*/
export interface LineSpec extends ShapeSpec {
/** Starting position */
start?: XYZ;
/** Ending position */
end?: XYZ;
/** make dashed */
dashed?: boolean;
};
/**
* Box shape specification.
* @extends ShapeSpec
*/
export interface BoxSpec extends ShapeSpec {
/** bottom corner of box */
corner?: XYZ;
/** center of box */
center?: XYZ;
/** width, height, depth of box */
dimensions?: {
w: number | XYZ;
h: number | XYZ;
d: number | XYZ;
};
};
/**
* Specification for adding custom shape.
* @extends ShapeSpec
*/
export interface CustomShapeSpec extends ShapeSpec {
/** List of vertex positions */
vertexArr?: XYZ[];
/** List of normal vectors for each vertex */
normalArr?: XYZ[];
/** List of triangles to build the custom shape. Each triangle is defined by the indices of 3 vertices in vertexArr, so the array length should be 3 times the number of faces. */
faceArr?: number[];
};
/**
* Sphere shape specification. Extends {@link ShapeSpec}.
*/
export interface SphereSpec extends ShapeSpec {
/** center of sphere */
center?: XYZ;
/** radius of sphere */
radius?: number;
/** quality metric, higher uses more triangles (default 2) */
quality?: number;
};