/*
* This file is part of MoleculeViewer.
*
* MoleculeViewer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MoleculeViewer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MoleculeViewer. If not, see <http://www.gnu.org/licenses/>.
*/
package astex;
import java.util.List;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
/**
* Class for creating schematic diagrams.
*/
public class Schematic {
/** Create a schematic. */
public static Tmesh create(Arguments args, MoleculeRenderer mr, List<Atom> atoms){
String name = args.getString("-name", "defaultSchematic");
Tmesh tm = new Tmesh();
tm.setName(name);
AtomIterator iterator = mr.getAtomIterator();
while(iterator.hasMoreElements()){
Atom atom = iterator.getNextAtom();
atom.setTemporarilySelected(false);
}
for(Atom atom : atoms){
atom.setTemporarilySelected(true);
}
try {
int moleculeCount = mr.getMoleculeCount();
for(int i = 0; i < moleculeCount; i++){
Molecule m = mr.getMolecule(i);
for(int c = 0; c < m.getChainCount(); c++){
Chain chain = m.getChain(c);
chainSchematic(args, tm, chain);
}
}
// generate exact normals for the schematic
// using the current ones as approximations.
tm.recalculateNormals();
}catch(Exception e){
e.printStackTrace();
}
return tm;
}
/** The points for the various objects. */
private static Point3d guides[] = null;
private static Point3d tangents[] = null;
private static Point3d width[] = null;
private static Point3d thick[] = null;
private static Point3d tmp[] = null;
private static Point3d t[] = null;
private static Point3d tout[] = null;
private static Point3d h[] = null;
private static Residue.SS type[] = null;
private static int colors[] = null;
private static int resids[] = null;
private static boolean widthInitialised[] = null;
private static int guideCount = 0;
private static int residCount = 0;
private static void ensureCapacity(Chain chain){
int resCount = chain.getResidueCount();
if(guides == null ||
resCount > guides.length){
guides = new Point3d[resCount];
tangents = new Point3d[resCount];
width = new Point3d[resCount];
thick = new Point3d[resCount];
tmp = new Point3d[resCount];
h = new Point3d[resCount];
t = new Point3d[resCount];
tout = new Point3d[resCount];
type = new Residue.SS[resCount];
colors = new int[resCount];
resids = new int[resCount];
widthInitialised = new boolean[resCount];
for(int i = 0; i < resCount; i++){
guides[i] = new Point3d();
tangents[i] = new Point3d();
width[i] = new Point3d();
thick[i] = new Point3d();
tmp[i] = new Point3d();
t[i] = new Point3d();
tout[i] = new Point3d();
h[i] = new Point3d();
}
}
}
/** Add schematic objects for this chain. */
private static void chainSchematic(Arguments args, Tmesh tm , Chain chain){
int minResidues = args.getInteger("-minchainresidues", 5);
if(chain.getResidueCount() < minResidues) return;
ensureCapacity(chain);
tm.setColorStyle(Tmesh.ColorStyle.TriangleColor);
guideCount = 0;
// store the calpha positions and secondary structure types
for(int r = 0; r < chain.getResidueCount(); r++){
Residue res = chain.getResidue(r);
Atom CA = res.getAtom("CA");
if(CA != null && CA.isTemporarilySelected()){
// if there is C and O for this res
// use it to define the width vector
Atom C = res.getAtom("C");
Atom O = res.getAtom("O");
if(O != null && C != null){
width[guideCount] = Point3d.unitVector(C, O);
widthInitialised[guideCount] = true;
}else{
widthInitialised[guideCount] = false;
}
colors[guideCount] = CA.getColor();
guides[guideCount].set(CA);
type[guideCount] = res.getSecondaryStructure();
guideCount++;
}
}
// MLV 17/09/07 DNA/RNA schematics
for(int r = 0; r < chain.getResidueCount(); r++){
Residue res = chain.getResidue(r);
Atom C5 = res.getAtom("C5*");
if(C5 != null && C5.isTemporarilySelected()){
widthInitialised[guideCount] = false;
colors[guideCount] = C5.getColor();
guides[guideCount].set(C5);
type[guideCount] = Residue.SS.Sheet;
guideCount++;
}
}
if(guideCount < 2){
return;
}
// calculate the tangents
for(int r = 0; r < guideCount; r++){
int before = r - 1;
int after = r + 1;
if(r == 0){
before = r;
}else if(r == guideCount - 1){
after = guideCount - 1;
}
tangents[r].zero();
tangents[r].sub(guides[before]);
tangents[r].add(guides[after]);
tangents[r].normalize();
}
// calculate out of plane normals
for(int r = 1; r < guideCount - 1; r++){
if(!widthInitialised[r]){
Point3d ab = Point3d.unitVector(guides[r-1], guides[r]);
Point3d bc = Point3d.unitVector(guides[r], guides[r+1]);
Point3d.cross(width[r], ab, bc);
}
}
// assign end points.
if(guideCount > 2){
if(!widthInitialised[0]){
width[0].set(width[1]);
}
if(!widthInitialised[guideCount - 1]){
width[guideCount-1].set(width[guideCount-2]);
}
}
// now go through the chain and generate
// the bits and pieces that make up the schematic
boolean allTube = args.getBoolean("-alltube", false);
if(allTube){
residCount = 0;
for(int r = 0; r < guideCount; r++){
resids[residCount] = r;
residCount++;
}
tube(args, tm);
}else{
residCount = 0;
// Sheet.
for(int r = 0; r < guideCount; r++){
if(type[r] == Residue.SS.Sheet){
resids[residCount] = r;
residCount++;
}else{
if(residCount > 0){
arrow(args, tm);
residCount = 0;
}
}
}
if(residCount > 0){
arrow(args, tm);
residCount = 0;
}
// Helix.
for(int r = 0; r < guideCount; r++){
if(type[r] == Residue.SS.Helix){
resids[residCount] = r;
residCount++;
}else{
if(residCount > 0){
ribbon(args, tm);
residCount = 0;
}
}
}
if(residCount > 0){
ribbon(args, tm);
residCount = 0;
}
// Coil.
for(int r = 0; r < guideCount; r++){
if((r > 0 && type[r] != Residue.SS.Coil &&
type[r - 1] == Residue.SS.Coil) ||
(r < guideCount - 1 && type[r] != Residue.SS.Coil
&& type[r + 1] == Residue.SS.Coil) ||
type[r] == Residue.SS.Coil){
if(residCount > 0 &&
guides[r].distance(guides[resids[residCount - 1]]) > 4.2){
tube(args, tm);
residCount = 0;
}
resids[residCount] = r;
residCount++;
if(r > 0 && type[r] != Residue.SS.Coil &&
type[r - 1] == Residue.SS.Coil){
tube(args, tm);
residCount = 0;
}
}else{
if(residCount > 0){
tube(args, tm);
residCount = 0;
}
}
}
if(residCount > 0){
tube(args, tm);
residCount = 0;
}
for(int r = 1; r < guideCount; r++){
if((type[r] == Residue.SS.Sheet && type[r-1] == Residue.SS.Helix) ||
(type[r-1] == Residue.SS.Sheet && type[r] == Residue.SS.Helix)){
resids[0] = r - 1;
resids[1] = r;
residCount = 2;
tube(args, tm);
residCount = 0;
}
}
}
}
// offset vectors
private static Point3d wptp = new Point3d();
private static Point3d wptm = new Point3d();
private static Point3d wmtp = new Point3d();
private static Point3d wmtm = new Point3d();
private static Point3d wm = new Point3d();
private static Point3d wp = new Point3d();
private static Point3d ep = new Point3d();
private static Point3d en = new Point3d();
private static Point3d wmlast = new Point3d();
private static Point3d wplast = new Point3d();
private static Point3d p = new Point3d();
private static Point3d wint = new Point3d();
private static Point3d tint = new Point3d();
private static int lastv[] = new int[8];
private static int v[] = new int[8];
// points on end of strip...
private static int hv[] = new int[4];
// rotation constants for helix vectors
// (from molscript paper)
private static final double COSA = 0.8480;
private static final double COSB = 0.9816;
private static final double SINA = 0.5299;
private static final double SINB = 0.1908;
/** Add ribbon to graphical object. */
private static void ribbon(Arguments args, Tmesh tm){
int quality = args.getInteger("-quality", 1);
double tangent_length = args.getDouble("-ribbontangent", 5.0);
int splinePoints = args.getInteger("-ribbonpoints", 8);
double aWidth = 0.5 * args.getDouble("-ribbonwidth", 2.2);
double minWidth = 0.5 * args.getDouble("-ribbonminwidth", 0.4);
double aThick = 0.5 * args.getDouble("-ribbonthickness", 0.15);
boolean cylinders = args.getBoolean("-ribboncylinders", false);
boolean helixBack = args.getBoolean("-helixback", false);
boolean ellipse = args.getBoolean("-ribbonellipse", false);
int ellipsePoints = args.getInteger("-ribbonellipsepoints", 12);
boolean colorBySS = args.getBoolean("-colorbyss", false);
int color = args.getColor("-ribboncolor", Color32.red);
splinePoints *= quality;
ellipsePoints *= quality;
int rfirst = resids[0];
int rlast = resids[residCount - 1];
Point3d plast = new Point3d();
Point3d p = new Point3d();
Point3d pnext = new Point3d();
int currentEllipse[] = null;
int lastEllipse[] = null;
double cosTheta[] = null;
double sinTheta[] = null;
if(ellipse){
currentEllipse = new int[ellipsePoints];
lastEllipse = new int[ellipsePoints];
// build the trig lookup table
sinTheta = new double[ellipsePoints];
cosTheta = new double[ellipsePoints];
double step = 2.*Math.PI/ellipsePoints;
double theta = 0.0;
for(int iep = 0; iep < ellipsePoints; iep++){
// skew the points to the areas around the ellipse
double component = 0.0;
if(theta < Math.PI){
component = -Math.cos(theta) + 1.0;
}else{
component = Math.cos(theta) + 3.0;
}
// factor of two comes from the component already.
double realTheta = 0.5 * component * Math.PI;
sinTheta[iep] = Math.sin(realTheta);
cosTheta[iep] = Math.cos(realTheta);
theta += step;
}
}
boolean fudgeFirst = false;
boolean fudgeLast = false;
for(int i = 0; i < residCount ; i++){
int r = resids[i];
if(r == 0){
fudgeFirst = true;
}else if(r == guideCount - 1){
fudgeLast = true;
}else{
Point3d ab = Point3d.unitVector(guides[r-1], guides[r]);
Point3d bc = Point3d.unitVector(guides[r], guides[r+1]);
Point3d.cross(width[r], ab, bc);
}
}
if(fudgeFirst){
width[rfirst].set(width[rfirst+1]);
}
if(fudgeLast){
width[rlast].set(width[rlast-1]);
}
// rotate vectors slightly as in molscript
// to get correct shape.
for(int i = 0; i < residCount; i++){
int r = resids[i];
for(int j = 0; j < 3; j++){
t[r].set(j, COSB * tangents[r].get(j) - SINB * width[r].get(j));
h[r].set(j, COSA * width[r].get(j) + SINA * tangents[r].get(j));
}
t[r].normalize();
h[r].normalize();
Point3d.cross(tout[r], t[r], h[r]);
}
boolean first = true;
boolean firstVertices = true;
for(int i = 0; i < residCount - 1; i++){
int r = resids[i];
double w = aWidth;
double at = aThick;
int nsp = splinePoints;
if(i == residCount - 2){
nsp = splinePoints + 1;
}
for(int sp = 0; sp < nsp; sp++){
double tp = (double)sp/(double)splinePoints;
double tnext = (double)(sp+1)/(double)splinePoints;
// new color interpolation scheme...
// blame joe
if(!colorBySS){
color = Color32.blend(colors[r], colors[r+1], 1. - tp);
}
if(i == 0){
w = minWidth + tp * (aWidth - minWidth);
at = minWidth + tp * ( aThick - minWidth);
}else if(i == residCount - 2){
w = minWidth + (1. - tp) * (aWidth - minWidth);
at = minWidth + (1. - tp) * ( aThick - minWidth);
}
interpolate(wint, h[r], h[r+1], tp);
hermite_single(guides[r], guides[r+1],
t[r], tangent_length,
t[r+1], tangent_length,
tp, p);
hermite_single(guides[r], guides[r+1],
t[r], tangent_length,
t[r+1], tangent_length,
tnext, pnext);
interpolate(tint, tout[r], tout[r+1], tp);
if(ellipse){
for(int iep = 0; iep < ellipsePoints; iep++){
double ct = cosTheta[iep];
double st = sinTheta[iep];
for(int c = 0; c < 3; c++){
ep.set(c, p.get(c) + ct * wint.get(c) * w + st * tint.get(c) * at);
// normal to ellipse, just swap components
en.set(c, ct * wint.get(c) * at + st * tint.get(c) * w);
}
en.normalize();
currentEllipse[iep] = tm.addPoint(ep.x, ep.y, ep.z, en.x, en.y, en.z, 0.0, 0.0);
}
if(!first){
// connect up the triangles
for(int iep = 0; iep < ellipsePoints; iep++){
int inp = iep + 1;
if(inp == ellipsePoints){
inp = 0;
}
tm.addTriangle(currentEllipse[iep], lastEllipse[iep], currentEllipse[inp],
color);
tm.addTriangle(lastEllipse[iep], currentEllipse[inp], lastEllipse[inp],
color);
}
}
System.arraycopy(currentEllipse, 0, lastEllipse, 0, ellipsePoints);
}else{
// calculate perimeter of arrow cross section
for(int c = 0; c < 3; c++){
wptp.set(c, p.get(c) + wint.get(c) * w + tint.get(c) * aThick);
wptm.set(c, p.get(c) + wint.get(c) * w - tint.get(c) * aThick);
wmtp.set(c, p.get(c) - wint.get(c) * w + tint.get(c) * aThick);
wmtm.set(c, p.get(c) - wint.get(c) * w - tint.get(c) * aThick);
wp.set(c, p.get(c) + wint.get(c) * w);
wm.set(c, p.get(c) - wint.get(c) * w);
}
// add points for perimeter
// each point twice with different normal
v[0] = tm.addPoint(wptm.x, wptm.y, wptm.z, -tint.x, -tint.y, -tint.z, 0.0, 0.0);
v[1] = tm.addPoint(wmtm.x, wmtm.y, wmtm.z, -tint.x, -tint.y, -tint.z, 0.0, 0.0);
v[2] = tm.addPoint(wmtp.x, wmtp.y, wmtp.z, tint.x, tint.y, tint.z, 0.0, 0.0);
v[3] = tm.addPoint(wptp.x, wptp.y, wptp.z, tint.x, tint.y, tint.z, 0.0, 0.0);
if(!cylinders){
v[4] = tm.addPoint(wmtm.x, wmtm.y, wmtm.z, -wint.x, -wint.y, -wint.z, 0.0, 0.0);
v[5] = tm.addPoint(wmtp.x, wmtp.y, wmtp.z, -wint.x, -wint.y, -wint.z, 0.0, 0.0);
v[6] = tm.addPoint(wptp.x, wptp.y, wptp.z, wint.x, wint.y, wint.z, 0.0, 0.0);
v[7] = tm.addPoint(wptm.x, wptm.y, wptm.z, wint.x, wint.y, wint.z, 0.0, 0.0);
}
if(!firstVertices){
int end = 2;
if(!cylinders){
end = 4;
}
for(int k = 0; k < end; k++){
int tcolor = color;
if(k == 0 && helixBack){
tcolor = Color32.gray;
//tcolor = backColor;
}else{
tcolor = color;
}
tm.addTriangle(v[0+2*k], v[1+2*k], lastv[0+2*k], tcolor);
tm.addTriangle(v[1+2*k], lastv[0+2*k], lastv[1+2*k], tcolor);
}
double cylRadius = aThick * 1.5;
if(cylinders){
tm.addCylinder(wm.x, wm.y, wm.z,
wmlast.x, wmlast.y, wmlast.z,
cylRadius, color, color);
tm.addCylinder(wp.x, wp.y, wp.z,
wplast.x, wplast.y, wplast.z,
cylRadius, color, color);
}
}
System.arraycopy(v, 0, lastv, 0, 8);
}
wmlast.set(wm);
wplast.set(wp);
firstVertices = false;
plast.set(p);
first = false;
}
}
}
private static Point3d spline[] = null;
private static int splineColor[] = null;
private static void ensureSplineCapacity(int n){
if(spline == null || spline.length < n){
spline = new Point3d[n];
splineColor = new int[n];
}
for(int i = 0; i < n; i++){
spline[i] = new Point3d();
}
}
/** Add a tube to the object. */
private static void tube(Arguments args, Tmesh tm){
int quality = args.getInteger("-quality", 1);
int smooth = args.getInteger("-tubesmoothing", 1);
int splinePoints = args.getInteger("-tubepoints", 4);
int perimPoints = args.getInteger("-tubeperimeter", 8);
double radius = args.getDouble("-tuberadius", 0.2);
double rTaper = args.getDouble("-tubetaperradius", 0.1);
double tangent = args.getDouble("-tubetangent", 2.);
boolean taper = args.getBoolean("-tubetaper", false);
boolean colorBySS = args.getBoolean("-colorbyss", false);
int color = args.getColor("-tubecolor", Color32.white);
splinePoints *= quality;
perimPoints *= quality;
ensureSplineCapacity(residCount * splinePoints);
int rfirst = resids[0];
int rlast = resids[residCount - 1];
DoubleArrayList radii = null;
// apply guide point smoothing.
for(int iteration = 0; iteration < smooth; iteration++){
for(int i = 1; i < residCount - 1; i++){
int r = resids[i];
tmp[r].set(guides[r-1]);
tmp[r].add(guides[r+1]);
tmp[r].scale(0.5);
tmp[r].add(guides[r]);
tmp[r].scale(0.5);
}
for(int i = 1; i < residCount - 1; i++){
int r = resids[i];
guides[r].set(tmp[r]);
}
}
// calculate the tangents
for(int r = 1; r < guideCount-1; r++){
int before = r - 1;
int after = r + 1;
if(r == 0){
before = r;
}else if(r == guideCount - 1){
after = guideCount - 1;
}
tangents[r].zero();
tangents[r].sub(guides[before]);
tangents[r].add(guides[after]);
tangents[r].normalize();
}
boolean fudgeFirst = false;
boolean fudgeLast = false;
for(int i = 0; i < residCount ; i++){
int r = resids[i];
if(r == 0){
fudgeFirst = true;
}else if(r == guideCount - 1){
fudgeLast = true;
}else{
Point3d ab = Point3d.unitVector(guides[r-1], guides[r]);
Point3d bc = Point3d.unitVector(guides[r], guides[r+1]);
Point3d.cross(width[r], ab, bc);
}
}
if(residCount == 2){
Point3d.normalToLine(guides[rfirst], width[rfirst]);
Point3d.normalToLine(guides[rlast], width[rlast]);
}else{
if(fudgeFirst){
width[rfirst].set(width[rfirst+1]);
}
if(fudgeLast){
width[rlast].set(width[rlast-1]);
}
}
for(int i = 1; i < residCount; i++){
int r = resids[i];
if(width[r].dot(width[r-1]) < 0.0){
width[r].negate();
}
}
for(int i = 0; i < residCount; i++){
int r = resids[i];
Point3d.cross(thick[r], tangents[r], width[r]);
}
for(int i = 1; i < residCount; i++){
int r = resids[i];
if(thick[r].dot(thick[r-1]) < 0.0){
thick[r].negate();
}
}
int nsp = 0;
boolean first = true;
Point3d pos = new Point3d();
int pnew[] = new int[perimPoints];
int plast[] = new int[perimPoints];
if(taper){
radii = new DoubleArrayList();
}
// build the trig lookup table
double sinTheta[] = new double[perimPoints];
double cosTheta[] = new double[perimPoints];
for(int iep = 0; iep < perimPoints; iep++){
double theta = 2.* Math.PI*(double)iep/(double)perimPoints;
sinTheta[iep] = Math.sin(theta);
cosTheta[iep] = Math.cos(theta);
}
for(int i = 0; i < residCount - 1; i++){
int r = resids[i];
int sp1 = splinePoints;
if(i == residCount - 2){
sp1 = splinePoints + 1;
}
for(int sp = 0; sp < sp1; sp++){
double t = (double)sp/(double)(splinePoints);
// if we aren't smoothing force
// spline points to lie near curved sections.
if(smooth == 0){
if(t < 0.5){
t *= t;
}else{
t = 1.0 - t;
t *= t;
t = 1.0 - t;
}
}
if(taper){
double rr = radius;
if(i == 0){
if(t < 0.5){
rr = rTaper + 2.*t * (radius - rTaper);
}
}else if((i == residCount - 2) &&
t > 0.5){
rr = rTaper + 2.*(1.0 - t) * (radius - rTaper);
}
radii.add(rr);
}
// new color interpolation scheme...
// blame joe
if(!colorBySS){
color = Color32.blend(colors[r], colors[r+1], 1. - t);
}
splineColor[nsp] = color;
hermite_single(guides[r], guides[r+1],
tangents[r], tangent,
tangents[r+1], tangent,
t, spline[nsp]);
nsp++;
}
}
Point3d alast = null;
Point3d blast = null;
for(int isp = 0; isp < nsp; isp++){
Point3d p = spline[isp];
Point3d a = null;
Point3d b = null;
if(alast == null){
if(isp == 0){
Point3d dir = Point3d.unitVector(spline[0], spline[1]);
a = Point3d.normalToLine(dir);
b = a.cross(dir);
}else{
Point3d ab = Point3d.unitVector(spline[isp-1], spline[isp]);
Point3d bc = Point3d.unitVector(spline[isp], spline[isp+1]);
Point3d dir = Point3d.unitVector(spline[isp-1], spline[isp+1]);
a = ab.cross(bc);
b = a.cross(dir);
}
}else{
Point3d dir = null;
if(isp == nsp - 1){
dir = Point3d.unitVector(spline[isp-1], spline[isp]);
}else{
dir = Point3d.unitVector(spline[isp-1], spline[isp+1]);
}
b = dir.cross(alast);
a = b.cross(dir);
if(a.dot(alast) < 0.0) a.negate();
if(b.dot(blast) < 0.0) b.negate();
}
double rr = radius;
if(taper){
rr = radii.getDouble(isp);
}
color = splineColor[isp];
for(int ip = 0; ip < perimPoints; ip++){
double costheta = cosTheta[ip];
double sintheta = sinTheta[ip];
for(int j = 0; j < 3; j++){
pos.set(j, a.get(j) * costheta + b.get(j) * sintheta);
}
pnew[ip] = tm.addPoint(p.x + pos.x * rr,
p.y + pos.y * rr,
p.z + pos.z * rr,
pos.x, pos.y, pos.z,
0.0, 0.0);
}
if(!first){
for(int ip = 0; ip < perimPoints; ip++){
int pnext = (ip + 1) % perimPoints;
tm.addTriangle(pnew[ip], plast[ip], pnew[pnext], color);
tm.addTriangle(plast[ip], pnew[pnext], plast[pnext], color);
}
}else{
double ptmp[] = new double[3];
double ntmp[] = new double[3];
int face[] = new int[perimPoints];
for(int ip = 0; ip < perimPoints; ip++){
tm.getVertex(pnew[ip], ptmp, ntmp);
face[ip] = tm.addPoint(ptmp[0], ptmp[1], ptmp[2],
-tangents[rfirst].x,
-tangents[rfirst].y,
-tangents[rfirst].z,
0.0, 0.0);
}
for(int ip = 2; ip < perimPoints; ip++){
tm.addTriangle(face[0], face[ip-1], face[ip], color);
}
}
System.arraycopy(pnew, 0, plast, 0, perimPoints);
first = false;
alast = a;
blast = b;
}
// block off the last face.
double ptmp[] = new double[3];
double ntmp[] = new double[3];
int face[] = new int[perimPoints];
for(int p = 0; p < perimPoints; p++){
tm.getVertex(pnew[p], ptmp, ntmp);
face[p] = tm.addPoint(ptmp[0], ptmp[1], ptmp[2],
tangents[rlast].x,
tangents[rlast].y,
tangents[rlast].z,
0.0, 0.0);
}
for(int p = 2; p < perimPoints; p++){
tm.addTriangle(face[0], face[p-1], face[p], color);
}
}
/** Add an arrow to the object. */
private static void arrow(Arguments args, Tmesh tm){
int quality = args.getInteger("-quality", 1);
int smooth = args.getInteger("-arrowsmoothing", 3);
int splinePoints = args.getInteger("-arrowpoints", 4);
double aHeadWidth = 0.5 * args.getDouble("-arrowheadwidth", 3.6);
double tangent = args.getDouble("-arrowtangent", 2.0);
double aWidth = 0.5 * args.getDouble("-arrowwidth", 2.2);
double aThick = 0.5 * args.getDouble("-arrowthickness", 0.5);
boolean colorBySS = args.getBoolean("-colorbyss", false);
int color = args.getColor("-arrowcolor", Color32.yellow);
splinePoints *= quality;
if(residCount <= 1) return;
// correct direction swaps
for(int i = 1; i < residCount; i++){
int r = resids[i];
if(width[r].dot(width[r-1]) < 0.0){
width[r].negate();
}
}
// smooth out of plane normals
//for(int iteration = 0; iteration < smooth; iteration++){
for(int iteration = 0; iteration < 1; iteration++){
for(int i = 0; i < residCount; i++){
int r = resids[i];
tmp[r].set(width[r]);
if(i != 0){
tmp[r].add(width[r-1]);
}else if(i < residCount - 1){
tmp[r].add(width[r+1]);
}
tmp[r].normalize();
}
// and reinstall them...
for(int i = 0; i < residCount; i++){
int r = resids[i];
width[r].set(tmp[r]);
}
}
// apply guide point smoothing.
for(int iteration = 0; iteration < smooth; iteration++){
for(int i = 1; i < residCount - 1; i++){
int r = resids[i];
tmp[r].set(guides[r-1]);
tmp[r].add(guides[r+1]);
tmp[r].scale(0.5);
tmp[r].add(guides[r]);
tmp[r].scale(0.5);
}
for(int i = 1; i < residCount - 1; i++){
int r = resids[i];
guides[r].set(tmp[r]);
}
}
Point3d ref = new Point3d();
// Generate in CA-CA-CA plane normal
for(int i = 0; i < residCount; i++){
int r = resids[i];
int before = r - 1;
int after = r + 1;
if(i == 0){
before = r;
}else if(i == residCount - 1){
after = r;
}
Point3d ab = Point3d.unitVector(guides[before], guides[after]);
Point3d.cross(thick[r], ab, width[r]);
// recalculate the tangent vectors to reflect smoothing.
tangents[r].set(ab);
// try and reorthogonalise the width vectors
Point3d.cross(ref, width[r], ab);
Point3d.cross(width[r], ref, ab);
width[r].normalize();
Point3d.cross(thick[r], ab, width[r]);
}
// Correct direction swaps.
for(int i = 1; i < residCount; i++){
int r = resids[i];
if(thick[r].dot(thick[r-1]) < 0.0){
thick[r].negate();
}
}
if(residCount > 2){
int last = resids[residCount - 1];
int prevLast = resids[residCount - 2];
width[last].set(width[prevLast]);
thick[last].set(thick[prevLast]);
}
boolean first = true;
for(int i = 0; i < residCount - 1; i++){
int r = resids[i];
double w = aWidth;
for(int sp = 0; sp < splinePoints; sp++){
double t = 0.0;
if(i == residCount - 3){
if(sp == splinePoints - 1){
t = 0.9;
}else{
t = (double)sp/(double)(splinePoints - 1);
}
}else if(i == residCount - 2){
t = (double)sp/(double)(splinePoints - 1);
}else{
t = (double)sp/(double)(splinePoints);
}
// new color interpolation scheme...
// blame joe
if(!colorBySS){
color = Color32.blend(colors[r], colors[r+1], 1. - t);
}
hermite_single(guides[r], guides[r+1],
tangents[r], tangent,
tangents[r+1], tangent,
t, p);
// on arrow tip
// scale width according to distance along vector.
// as hermite interpolation is not uniform
if(i == residCount - 2){
w = p.distance(guides[r+1])/guides[r+1].distance(guides[r]);
w *= aHeadWidth;
}
interpolate(wint, width[r], width[r+1], t);
interpolate(tint, thick[r], thick[r+1], t);
// calculate perimeter of arrow cross section
for(int c = 0; c < 3; c++){
wptp.set(c, p.get(c) + wint.get(c) * w + tint.get(c) * aThick);
wptm.set(c, p.get(c) + wint.get(c) * w - tint.get(c) * aThick);
wmtp.set(c, p.get(c) - wint.get(c) * w + tint.get(c) * aThick);
wmtm.set(c, p.get(c) - wint.get(c) * w - tint.get(c) * aThick);
}
// block off start of arrow
if(first){
double nx = -tangents[r].x;
double ny = -tangents[r].y;
double nz = -tangents[r].z;
v[0] = tm.addPoint(wptp.x, wptp.y, wptp.z, nx, ny, nz, 0.0, 0.0);
v[1] = tm.addPoint(wptm.x, wptm.y, wptm.z, nx, ny, nz, 0.0, 0.0);
v[2] = tm.addPoint(wmtp.x, wmtp.y, wmtp.z, nx, ny, nz, 0.0, 0.0);
v[3] = tm.addPoint(wmtm.x, wmtm.y, wmtm.z, nx, ny, nz, 0.0, 0.0);
tm.addTriangle(v[0], v[1], v[2], color);
tm.addTriangle(v[1], v[2], v[3], color);
}
// back of arrow...
if(i == residCount - 2 && sp == 0){
double nx = -tangents[r].x;
double ny = -tangents[r].y;
double nz = -tangents[r].z;
v[0] = tm.addPoint(wptp.x, wptp.y, wptp.z, nx, ny, nz, 0.0, 0.0);
v[1] = tm.addPoint(wptm.x, wptm.y, wptm.z, nx, ny, nz, 0.0, 0.0);
v[2] = tm.addPoint(wmtp.x, wmtp.y, wmtp.z, nx, ny, nz, 0.0, 0.0);
v[3] = tm.addPoint(wmtm.x, wmtm.y, wmtm.z, nx, ny, nz, 0.0, 0.0);
if(residCount > 2){
// one part of back facing tip
tm.addTriangle(v[0], v[1], hv[1], color);
tm.addTriangle(v[0], hv[0], hv[1], color);
// second part of back facing tip
tm.addTriangle(v[2], v[3], hv[3], color);
tm.addTriangle(v[2], hv[2], hv[3], color);
}else{
tm.addTriangle(v[0], v[1], v[2], color);
tm.addTriangle(v[0], v[2], v[3], color);
}
}
// last perimeter of main part of arrow...
if(i == residCount - 3 && sp == splinePoints - 1){
double nx = -tangents[r].x;
double ny = -tangents[r].y;
double nz = -tangents[r].z;
hv[0] = tm.addPoint(wptp.x, wptp.y, wptp.z, nx, ny, nz, 0.0, 0.0);
hv[1] = tm.addPoint(wptm.x, wptm.y, wptm.z, nx, ny, nz, 0.0, 0.0);
hv[2] = tm.addPoint(wmtp.x, wmtp.y, wmtp.z, nx, ny, nz, 0.0, 0.0);
hv[3] = tm.addPoint(wmtm.x, wmtm.y, wmtm.z, nx, ny, nz, 0.0, 0.0);
// triangles are added on first
// perimeter of arrow tip.
}
// add points for perimeter
// each point twice with different normal
v[0] = tm.addPoint(wptp.x, wptp.y, wptp.z, wint.x, wint.y, wint.z, 0.0, 0.0);
v[1] = tm.addPoint(wptm.x, wptm.y, wptm.z, wint.x, wint.y, wint.z, 0.0, 0.0);
v[2] = tm.addPoint(wptm.x, wptm.y, wptm.z, -tint.x, -tint.y, -tint.z, 0.0, 0.0);
v[3] = tm.addPoint(wmtm.x, wmtm.y, wmtm.z, -tint.x, -tint.y, -tint.z, 0.0, 0.0);
v[4] = tm.addPoint(wmtm.x, wmtm.y, wmtm.z, -wint.x, -wint.y, -wint.z, 0.0, 0.0);
v[5] = tm.addPoint(wmtp.x, wmtp.y, wmtp.z, -wint.x, -wint.y, -wint.z, 0.0, 0.0);
v[6] = tm.addPoint(wmtp.x, wmtp.y, wmtp.z, tint.x, tint.y, tint.z, 0.0, 0.0);
v[7] = tm.addPoint(wptp.x, wptp.y, wptp.z, tint.x, tint.y, tint.z, 0.0, 0.0);
if(!first){
// add triangles from this perimeter to last
if(i == residCount - 2 && sp == 0){
for(int k = 1; k < 4; k+=2){
tm.addTriangle(v[0+2*k], v[1+2*k], lastv[0+2*k], color);
tm.addTriangle(v[1+2*k], lastv[0+2*k], lastv[1+2*k], color);
}
}else{
for(int k = 0; k < 4; k++){
tm.addTriangle(v[0+2*k], v[1+2*k], lastv[0+2*k], color);
tm.addTriangle(v[1+2*k], lastv[0+2*k], lastv[1+2*k], color);
}
}
}
System.arraycopy(v, 0, lastv, 0, 8);
first = false;
}
}
}
/** Linear interpolation between p1 and p2. */
private static void interpolate(Point3d s,
Point3d p1, Point3d p2,
double t){
s.x = p1.x + t * (p2.x - p1.x);
s.y = p1.y + t * (p2.y - p1.y);
s.z = p1.z + t * (p2.z - p1.z);
s.normalize();
}
/** Interpolate hermite spline. */
private static void hermite_single(Point3d P1, Point3d P2,
Point3d T1, double T1len,
Point3d T2, double T2len,
double s, Point3d p){
double h1 = 2*s*s*s - 3*s*s + 1; // calculate basis function 1
double h2 = -2*s*s*s + 3*s*s; // calculate basis function 2
double h3 = s*s*s - 2*s*s + s; // calculate basis function 3
double h4 = s*s*s - s*s; // calculate basis function 4
h3 *= T1len;
h4 *= T2len;
p.x = h1*P1.x + h2*P2.x+ h3*T1.x + h4*T2.x;
p.y = h1*P1.y + h2*P2.y+ h3*T1.y + h4*T2.y;
p.z = h1*P1.z + h2*P2.z+ h3*T1.z + h4*T2.z;
}
}