1 /* $RCSfile$
2  * $Author: hansonr $
3  * $Date: 2017-05-29 21:30:21 -0500 (Mon, 29 May 2017) $
4  * $Revision: 21625 $
5 
6  *
7  * Copyright (C) 2002-2005  The Jmol Development Team
8  *
9  * Contact: jmol-developers@lists.sf.net
10  *
11  *  This library is free software; you can redistribute it and/or
12  *  modify it under the terms of the GNU Lesser General Public
13  *  License as published by the Free Software Foundation; either
14  *  version 2.1 of the License, or (at your option) any later version.
15  *
16  *  This library is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  *  Lesser General Public License for more details.
20  *
21  *  You should have received a copy of the GNU Lesser General Public
22  *  License along with this library; if not, write to the Free Software
23  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24  */
25 
26 package org.jmol.render;
27 
28 import org.jmol.c.PAL;
29 import javajs.util.BS;
30 import org.jmol.modelset.Atom;
31 import org.jmol.modelset.Bond;
32 import org.jmol.script.T;
33 import org.jmol.util.C;
34 import org.jmol.util.GData;
35 import org.jmol.util.Edge;
36 
37 import javajs.util.A4;
38 import javajs.util.M3;
39 import javajs.util.P3;
40 import javajs.util.V3;
41 import org.jmol.viewer.JC;
42 
43 public class SticksRenderer extends FontLineShapeRenderer {
44 
45   private boolean showMultipleBonds;
46   private float multipleBondSpacing;
47   private float multipleBondRadiusFactor;
48   private boolean bondsPerp;
49   private boolean useBananas;
50   private byte modeMultipleBond;
51   private boolean isCartesian;
52   //boolean showHydrogens;
53   private byte endcaps;
54 
55   private boolean ssbondsBackbone;
56   private boolean hbondsBackbone;
57   private boolean bondsBackbone;
58   private boolean hbondsSolid;
59 
60   private Atom a, b;
61   private Bond bond;
62   private int xA, yA, zA;
63   private int xB, yB, zB;
64   private int dx, dy;
65   private int mag2d;
66   private int bondOrder;
67   private boolean wireframeOnly;
68   private boolean isAntialiased;
69   private boolean slabbing;
70   private boolean slabByAtom;
71 
72   private final V3 x = new V3();
73   private final V3 y = new V3();
74   private final V3 z = new V3();
75   private final P3 p1 = new P3();
76   private final P3 p2 = new P3();
77   private final BS bsForPass2 = BS.newN(64);
78   private boolean isPass2;
79   private double rTheta;
80 
81   @Override
render()82   protected boolean render() {
83     Bond[] bonds = ms.bo;
84     if (bonds == null)
85       return false;
86     isPass2 = vwr.gdata.isPass2;
87     if (!isPass2)
88       bsForPass2.clearAll();
89     slabbing = tm.slabEnabled;
90     slabByAtom = vwr.getBoolean(T.slabbyatom);
91     endcaps = GData.ENDCAPS_SPHERICAL;
92     dashDots = (vwr.getBoolean(T.partialdots) ? sixdots : dashes);
93     isCartesian = (exportType == GData.EXPORT_CARTESIAN);
94     getMultipleBondSettings(false);
95     wireframeOnly = !vwr.checkMotionRendering(T.bonds);
96     ssbondsBackbone = vwr.getBoolean(T.ssbondsbackbone);
97     hbondsBackbone = vwr.getBoolean(T.hbondsbackbone);
98     bondsBackbone = hbondsBackbone | ssbondsBackbone;
99     hbondsSolid = vwr.getBoolean(T.hbondssolid);
100     isAntialiased = g3d.isAntialiased();
101     boolean needTranslucent = false;
102     if (isPass2) {
103       if (!isExport)
104         for (int i = bsForPass2.nextSetBit(0); i >= 0; i = bsForPass2
105             .nextSetBit(i + 1)) {
106           bond = bonds[i];
107           renderBond();
108         }
109     } else {
110       for (int i = ms.bondCount; --i >= 0;) {
111         bond = bonds[i];
112         if ((bond.shapeVisibilityFlags & myVisibilityFlag) != 0 && renderBond()) {
113           needTranslucent = true;
114           bsForPass2.set(i);
115         }
116       }
117     }
118     return needTranslucent;
119   }
120 
getMultipleBondSettings(boolean isPymol)121   private void getMultipleBondSettings(boolean isPymol) {
122     useBananas = (vwr.getBoolean(T.multiplebondbananas) && !isPymol);
123     // negative spacing is relative, depending upon atom-atom distance;
124     // positive spacing is absolute, for fixed in-plane (radiusFactor > 0) or perp-plane (radiusFactor < 0)
125     multipleBondSpacing = (isPymol ? 0.15f : vwr
126         .getFloat(T.multiplebondspacing));
127     // negative radius factor indicates perpendicular fixed double bond
128     multipleBondRadiusFactor = (isPymol ? 0.4f : vwr
129         .getFloat(T.multiplebondradiusfactor));
130     bondsPerp = (useBananas || multipleBondSpacing > 0
131         && multipleBondRadiusFactor < 0);
132     if (useBananas)
133       multipleBondSpacing = (multipleBondSpacing < 0 ? -multipleBondSpacing * 0.4f
134           : multipleBondSpacing);
135     multipleBondRadiusFactor = Math.abs(multipleBondRadiusFactor);
136     if (multipleBondSpacing == 0 && isCartesian)
137       multipleBondSpacing = 0.2f;
138     modeMultipleBond = vwr.g.modeMultipleBond;
139     showMultipleBonds = (multipleBondSpacing != 0
140         && modeMultipleBond != JC.MULTIBOND_NEVER && vwr
141         .getBoolean(T.showmultiplebonds));
142   }
143 
renderBond()144   private boolean renderBond() {
145     Atom atomA0, atomB0;
146 
147     a = atomA0 = bond.atom1;
148     b = atomB0 = bond.atom2;
149 
150     int order = bond.order & ~Edge.BOND_NEW;
151     if (bondsBackbone) {
152       if (ssbondsBackbone && (order & Edge.BOND_SULFUR_MASK) != 0) {
153         // for ssbonds, always render the sidechain,
154         // then render the backbone version
155         /*
156          mth 2004 04 26
157          No, we are not going to do this any more
158          render(bond, atomA, atomB);
159          */
160 
161         a = a.group.getLeadAtomOr(a);
162         b = b.group.getLeadAtomOr(b);
163       } else if (hbondsBackbone && Edge.isOrderH(order)) {
164         a = a.group.getLeadAtomOr(a);
165         b = b.group.getLeadAtomOr(b);
166       }
167     }
168     if (!isPass2
169         && (!a.isVisible(Atom.ATOM_INFRAME_NOTHIDDEN)
170             || !b.isVisible(Atom.ATOM_INFRAME_NOTHIDDEN)
171             || !g3d.isInDisplayRange(a.sX, a.sY) || !g3d.isInDisplayRange(b.sX,
172             b.sY)))
173       return false;
174 
175     if (slabbing) {
176       boolean ba = vwr.gdata.isClippedZ(a.sZ);
177       if (ba && vwr.gdata.isClippedZ(b.sZ) || slabByAtom
178           && (ba || vwr.gdata.isClippedZ(b.sZ)))
179         return false;
180     }
181     zA = a.sZ;
182     zB = b.sZ;
183     if (zA == 1 || zB == 1)
184       return false;
185     colixA = atomA0.colixAtom;
186     colixB = atomB0.colixAtom;
187     if (((colix = bond.colix) & C.OPAQUE_MASK) == C.USE_PALETTE) {
188       colix = (short) (colix & ~C.OPAQUE_MASK);
189       colixA = C.getColixInherited(
190           (short) (colix | vwr.cm.getColixAtomPalette(atomA0, PAL.CPK.id)),
191           colixA);
192       colixB = C.getColixInherited(
193           (short) (colix | vwr.cm.getColixAtomPalette(atomB0, PAL.CPK.id)),
194           colixB);
195     } else {
196       colixA = C.getColixInherited(colix, colixA);
197       colixB = C.getColixInherited(colix, colixB);
198     }
199     boolean needTranslucent = false;
200     if (!isExport && !isPass2) {
201       boolean doA = !C.renderPass2(colixA);
202       boolean doB = !C.renderPass2(colixB);
203       if (!doA || !doB) {
204         if (!doA && !doB && !needTranslucent) {
205           g3d.setC(!doA ? colixA : colixB);
206           return true;
207         }
208         needTranslucent = true;
209       }
210     }
211 
212     // set the rendered bond order
213 
214     bondOrder = order & ~Edge.BOND_NEW;
215     if ((bondOrder & Edge.BOND_PARTIAL_MASK) == 0) {
216       if ((bondOrder & Edge.BOND_SULFUR_MASK) != 0)
217         bondOrder &= ~Edge.BOND_SULFUR_MASK;
218       if ((bondOrder & Edge.BOND_COVALENT_MASK) != 0) {
219         if (!showMultipleBonds
220             || (modeMultipleBond == JC.MULTIBOND_NOTSMALL && mad > JC.madMultipleBondSmallMaximum)
221             || (bondOrder & Edge.BOND_PYMOL_MULT) == Edge.BOND_RENDER_SINGLE) {
222           bondOrder = 1;
223         }
224       }
225     }
226 
227     // set the mask
228 
229     int mask = 0;
230     switch (bondOrder) {
231     case Edge.BOND_STEREO_NEAR:
232     case Edge.BOND_STEREO_FAR:
233       bondOrder = 1;
234       //$FALL-THROUGH$
235     case 1:
236     case 2:
237     case 3:
238     case 4:
239     case 5:
240     case 6:
241       break;
242     case Edge.BOND_ORDER_UNSPECIFIED:
243     case Edge.BOND_AROMATIC_SINGLE:
244       bondOrder = 1;
245       mask = (order == Edge.BOND_AROMATIC_SINGLE ? 0 : 1);
246       break;
247     case Edge.BOND_AROMATIC:
248     case Edge.BOND_AROMATIC_DOUBLE:
249       bondOrder = 2;
250       mask = (order == Edge.BOND_AROMATIC ? getAromaticDottedBondMask() : 0);
251       break;
252     default:
253       if ((bondOrder & Edge.BOND_PARTIAL_MASK) != 0) {
254         bondOrder = Edge.getPartialBondOrder(order);
255         mask = Edge.getPartialBondDotted(order);
256       } else if (Edge.isOrderH(bondOrder)) {
257         bondOrder = 1;
258         if (!hbondsSolid)
259           mask = -1;
260       } else if (bondOrder == Edge.BOND_STRUT) {
261         bondOrder = 1;
262       } else if ((bondOrder & Edge.BOND_PYMOL_MULT) == Edge.BOND_PYMOL_MULT) {
263         getMultipleBondSettings(true);
264         bondOrder &= 3;
265         mask = -2;
266       }
267     }
268 
269     // set the diameter
270 
271     xA = a.sX;
272     yA = a.sY;
273     xB = b.sX;
274     yB = b.sY;
275 
276     mad = bond.mad;
277     if (multipleBondRadiusFactor > 0 && bondOrder > 1)
278       mad *= multipleBondRadiusFactor;
279     dx = xB - xA;
280     dy = yB - yA;
281     width = (int) vwr.tm.scaleToScreen((zA + zB) / 2, mad);
282     if (wireframeOnly && width > 0)
283       width = 1;
284     if (!isCartesian) {
285       asLineOnly = (width <= 1);
286       if (asLineOnly && (isAntialiased)) {
287         width = 3;
288         asLineOnly = false;
289       }
290     }
291 
292     // draw the bond
293 
294     switch (mask) {
295     case -2:
296       drawBond(0);
297       getMultipleBondSettings(false);
298       break;
299     case -1:
300       drawDashed(xA, yA, zA, xB, yB, zB, hDashes);
301       break;
302     default:
303       switch (bondOrder) {
304       case 4: {
305         bondOrder = 2;
306         float f = multipleBondRadiusFactor;
307         if (f == 0 && width > 1)
308           width = (int) (width * 0.5);
309         float m = multipleBondSpacing;
310         if (m < 0)
311           multipleBondSpacing = 0.30f;
312         drawBond(mask);
313         bondsPerp = !bondsPerp;
314         bondOrder = 2;
315         drawBond(mask >> 2);
316         bondsPerp = !bondsPerp;
317         multipleBondSpacing = m;
318       }
319         break;
320       case 5: {
321         bondOrder = 3;
322         float f = multipleBondRadiusFactor;
323         if (f == 0 && width > 1)
324           width = (int) (width * 0.5);
325         float m = multipleBondSpacing;
326         if (m < 0)
327           multipleBondSpacing = 0.20f;
328         drawBond(mask);
329         bondsPerp = !bondsPerp;
330         bondOrder = 2;
331         multipleBondSpacing *= 1.5f;
332         drawBond(mask >> 3);
333         bondsPerp = !bondsPerp;
334         multipleBondSpacing = m;
335       }
336         break;
337       case 6: {
338         bondOrder = 4;
339         float f = multipleBondRadiusFactor;
340         if (f == 0 && width > 1)
341           width = (int) (width * 0.5);
342         float m = multipleBondSpacing;
343         if (m < 0)
344           multipleBondSpacing = 0.15f;
345         drawBond(mask);
346         bondsPerp = !bondsPerp;
347         bondOrder = 2;
348         multipleBondSpacing *= 1.5f;
349         drawBond(mask >> 4);
350         bondsPerp = !bondsPerp;
351         multipleBondSpacing = m;
352       }
353         break;
354       default:
355         drawBond(mask);
356       }
357       break;
358     }
359     return needTranslucent;
360   }
361 
drawBond(int dottedMask)362   private void drawBond(int dottedMask) {
363     boolean isDashed = (dottedMask & 1) != 0;
364     if (isCartesian && bondOrder == 1 && !isDashed) {
365       // bypass screen rendering and just use the atoms themselves
366       g3d.drawBond(a, b, colixA, colixB, endcaps, mad, -1);
367       return;
368     }
369     boolean isEndOn = (dx == 0 && dy == 0);
370     if (isEndOn && asLineOnly && !isCartesian)
371       return;
372     boolean doFixedSpacing = (bondOrder > 1 && multipleBondSpacing > 0);
373     boolean isPiBonded = doFixedSpacing
374         && (vwr.getHybridizationAndAxes(a.i, z, x, "pz") != null || vwr
375             .getHybridizationAndAxes(b.i, z, x, "pz") != null)
376         && !Float.isNaN(x.x);
377     if (isEndOn && !doFixedSpacing) {
378       // end-on view
379       int space = width / 8 + 3;
380       int step = width + space;
381       int y = yA - (bondOrder - 1) * step / 2;
382       do {
383         fillCylinder(colixA, colixB, endcaps, width, xA, y, zA, xB, y, zB);
384         y += step;
385       } while (--bondOrder > 0);
386       return;
387     }
388     if (bondOrder == 1) {
389       if (isDashed)
390         drawDashed(xA, yA, zA, xB, yB, zB, dashDots);
391       else
392         fillCylinder(colixA, colixB, endcaps, width, xA, yA, zA, xB, yB, zB);
393       return;
394     }
395     if (doFixedSpacing) {
396       if (!isPiBonded) // obscure point
397         z.setT(P3.getUnlikely());
398       x.sub2(b, a);
399       y.cross(x, z);
400       y.normalize();
401       if (Float.isNaN(y.x)) {
402         // in case x and z are parallel (O=C=O)
403         z.setT(P3.getUnlikely());
404         y.cross(x, z);
405         y.cross(y, x);
406         y.normalize();
407       }
408       if (bondsPerp)
409         y.cross(y, x);
410       y.scale(multipleBondSpacing);
411       x.setT(y);
412       x.scale((bondOrder - 1) / 2f);
413       if (useBananas) {
414         drawBanana(a, b, x, 0);
415         switch (bondOrder) {
416         case 4:
417           drawBanana(a, b, x, 90);
418           drawBanana(a, b, x, -90);
419           //$FALL-THROUGH$
420         case 2:
421         default:
422           drawBanana(a, b, x, 180);
423           break;
424         case 3:
425           drawBanana(a, b, x, 120);
426           drawBanana(a, b, x, -120);
427           break;
428         }
429         return;
430       }
431       p1.sub2(a, x);
432       p2.sub2(b, x);
433 
434       while (true) {
435         if (isCartesian) {
436           // bypass screen rendering and just use the atoms themselves
437           g3d.drawBond(p1, p2, colixA, colixB, endcaps, mad, -2);
438         } else {
439           tm.transformPtScr(p1, s1);
440           tm.transformPtScr(p2, s2);
441           if (isDashed)
442             drawDashed(s1.x, s1.y, s1.z, s2.x, s2.y, s2.z, dashDots);
443           else
444             fillCylinder(colixA, colixB, endcaps, width, s1.x, s1.y, s1.z,
445                 s2.x, s2.y, s2.z);
446         }
447         dottedMask >>= 1;
448         isDashed = (dottedMask & 1) != 0;
449         if (--bondOrder <= 0)
450           break;
451         p1.add(y);
452         p2.add(y);
453         stepAxisCoordinates();
454       }
455       return;
456     }
457     int dxB = dx * dx;
458     int dyB = dy * dy;
459     mag2d = (int) Math.round(Math.sqrt(dxB + dyB));
460     resetAxisCoordinates();
461     if (isCartesian && bondOrder == 3) {
462       fillCylinder(colixA, colixB, endcaps, width, xAxis1, yAxis1, zA, xAxis2,
463           yAxis2, zB);
464       stepAxisCoordinates();
465       x.sub2(b, a);
466       x.scale(0.05f);
467       p1.sub2(a, x);
468       p2.add2(b, x);
469       g3d.drawBond(p1, p2, colixA, colixB, endcaps, mad, -2);
470       stepAxisCoordinates();
471       fillCylinder(colixA, colixB, endcaps, width, xAxis1, yAxis1, zA, xAxis2,
472           yAxis2, zB);
473       return;
474     }
475     while (true) {
476       if ((dottedMask & 1) != 0)
477         drawDashed(xAxis1, yAxis1, zA, xAxis2, yAxis2, zB, dashDots);
478       else
479         fillCylinder(colixA, colixB, endcaps, width, xAxis1, yAxis1, zA,
480             xAxis2, yAxis2, zB);
481       dottedMask >>= 1;
482       if (--bondOrder <= 0)
483         break;
484       stepAxisCoordinates();
485     }
486   }
487 
488   private int xAxis1, yAxis1, xAxis2, yAxis2, dxStep, dyStep;
489 
resetAxisCoordinates()490   private void resetAxisCoordinates() {
491     int space = mag2d >> 3;
492     if (multipleBondSpacing != -1 && multipleBondSpacing < 0)
493       space *= -multipleBondSpacing;
494     int step = width + space;
495     dxStep = step * dy / mag2d;
496     dyStep = step * -dx / mag2d;
497     xAxis1 = xA;
498     yAxis1 = yA;
499     xAxis2 = xB;
500     yAxis2 = yB;
501     int f = (bondOrder - 1);
502     xAxis1 -= dxStep * f / 2;
503     yAxis1 -= dyStep * f / 2;
504     xAxis2 -= dxStep * f / 2;
505     yAxis2 -= dyStep * f / 2;
506   }
507 
stepAxisCoordinates()508   private void stepAxisCoordinates() {
509     xAxis1 += dxStep;
510     yAxis1 += dyStep;
511     xAxis2 += dxStep;
512     yAxis2 += dyStep;
513   }
514 
getAromaticDottedBondMask()515   private int getAromaticDottedBondMask() {
516     Atom atomC = b.findAromaticNeighbor(a.i);
517     if (atomC == null)
518       return 1;
519     int dxAC = atomC.sX - xA;
520     int dyAC = atomC.sY - yA;
521     return ((dx * dyAC - dy * dxAC) < 0 ? 2 : 1);
522   }
523 
524   private M3 rot;
525   private A4 a4;
526 
drawBanana(Atom a, Atom b, V3 x, int deg)527   private void drawBanana(Atom a, Atom b, V3 x, int deg) {
528     g3d.addRenderer(T.hermitelevel);
529     vectorT.sub2(b, a);
530     if (rot == null) {
531       rot = new M3();
532       a4 = new A4();
533     }
534     a4.setVA(vectorT, (float) (deg * Math.PI / 180));
535     rot.setAA(a4);
536     pointT.setT(a);
537     pointT3.setT(b);
538     pointT2.ave(a, b);
539     rot.rotate2(x, vectorT);
540     pointT2.add(vectorT);
541     tm.transformPtScrT3(a, pointT);
542     tm.transformPtScrT3(pointT2, pointT2);
543     tm.transformPtScrT3(b, pointT3);
544     int w = Math.max(width, 1);
545     g3d.setC(colixA);
546     g3d.fillHermite(5, w, w, w, pointT, pointT, pointT2, pointT3);
547     g3d.setC(colixB);
548     g3d.fillHermite(5, w, w, w, pointT, pointT2, pointT3, pointT3);
549   }
550 
551 }
552