View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2023.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.potential.bonded;
39  
40  import static ffx.numerics.math.DoubleMath.X;
41  import static ffx.numerics.math.DoubleMath.angle;
42  import static ffx.numerics.math.DoubleMath.length;
43  import static ffx.numerics.math.DoubleMath.normalize;
44  import static ffx.numerics.math.DoubleMath.scale;
45  import static ffx.numerics.math.DoubleMath.sub;
46  import static java.lang.String.format;
47  import static org.apache.commons.math3.util.FastMath.max;
48  import static org.apache.commons.math3.util.FastMath.min;
49  
50  import ffx.numerics.atomic.AtomicDoubleArray3D;
51  import ffx.numerics.math.Double3;
52  import ffx.numerics.math.DoubleMath;
53  import ffx.potential.bonded.RendererCache.ViewModel;
54  import ffx.potential.parameters.AtomType;
55  import ffx.potential.parameters.BondType;
56  import ffx.potential.parameters.ForceField;
57  
58  import java.io.Serial;
59  import java.util.ArrayList;
60  import java.util.List;
61  import java.util.logging.Logger;
62  
63  import org.jogamp.java3d.BranchGroup;
64  import org.jogamp.java3d.Geometry;
65  import org.jogamp.java3d.LineArray;
66  import org.jogamp.java3d.Shape3D;
67  import org.jogamp.java3d.Transform3D;
68  import org.jogamp.java3d.TransformGroup;
69  import org.jogamp.vecmath.AxisAngle4d;
70  import org.jogamp.vecmath.Vector3d;
71  
72  /**
73   * The Bond class represents a covalent bond formed between two atoms.
74   *
75   * @author Michael J. Schnieders
76   * @since 1.0
77   */
78  @SuppressWarnings("CloneableImplementsClone")
79  public class Bond extends BondedTerm {
80  
81    @Serial
82    private static final long serialVersionUID = 1L;
83  
84    /**
85     * Length in Angstroms that is added to Atomic Radii when determining if two Atoms are within
86     * bonding distance
87     */
88    static final float BUFF = 0.7f;
89  
90    private static final Logger logger = Logger.getLogger(Bond.class.getName());
91    // Some static variables used for computing cylinder orientations
92    private static final float[] a0col = {
93        0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f
94    };
95    private static final float[] f4a = {0.0f, 0.0f, 0.0f, 0.9f};
96    private static final float[] f4b = {0.0f, 0.0f, 0.0f, 0.9f};
97    private static final float[] f16 = {
98        0.0f, 0.0f, 0.0f, 0.9f, 0.0f, 0.0f, 0.0f, 0.9f, 0.0f, 0.0f, 0.0f, 0.9f, 0.0f, 0.0f, 0.0f, 0.9f
99    };
100   private static final double[] a13d = new double[3];
101   private static final double[] a23d = new double[3];
102   private static final double[] mid = new double[3];
103   private static final double[] diff3d = new double[3];
104   private static final double[] sum3d = new double[3];
105   private static final double[] coord = new double[12];
106   private static final double[] y = {0.0d, 1.0d, 0.0d};
107   private static final AxisAngle4d axisAngle = new AxisAngle4d();
108   private static final double[] bcross = new double[4];
109   private static final double[] cstart = new double[3];
110   private static final Vector3d pos3d = new Vector3d();
111   /**
112    * List of Bonds that this Bond forms angles with
113    */
114   private final ArrayList<Bond> formsAngleWith = new ArrayList<>();
115   /**
116    * The force field BondType for this bond.
117    */
118   public BondType bondType = null;
119   /**
120    * Rigid Scale factor.
121    */
122   private double rigidScale = 1.0;
123   // Java3D methods and variables for visualization of this Bond.
124   private RendererCache.ViewModel viewModel = RendererCache.ViewModel.INVISIBLE;
125   private BranchGroup branchGroup;
126   private TransformGroup cy1tg, cy2tg;
127   private Transform3D cy1t3d, cy2t3d;
128   private Shape3D cy1, cy2;
129   private Vector3d scale;
130   private int detail = 3;
131   private LineArray la;
132   private int lineIndex;
133   private boolean wireVisible = true;
134 
135   /**
136    * Simple Bond constructor that is intended to be used with the <code>equals</code> method.
137    *
138    * @param n Bond id
139    */
140   public Bond(String n) {
141     super(n);
142   }
143 
144   /**
145    * Bond constructor.
146    *
147    * @param a1 Atom number 1.
148    * @param a2 Atom number 2.
149    */
150   public Bond(Atom a1, Atom a2) {
151     atoms = new Atom[2];
152     int i1 = a1.getIndex();
153     int i2 = a2.getIndex();
154     if (i1 < i2) {
155       atoms[0] = a1;
156       atoms[1] = a2;
157     } else {
158       atoms[0] = a2;
159       atoms[1] = a1;
160     }
161     setID_Key(false);
162     viewModel = RendererCache.ViewModel.WIREFRAME;
163     a1.setBond(this);
164     a2.setBond(this);
165   }
166 
167   /**
168    * Log that no BondType exists.
169    *
170    * @param a1         Atom 1.
171    * @param a2         Atom 2.
172    * @param forceField The force field in use.
173    */
174   public static void logNoBondType(Atom a1, Atom a2, ForceField forceField) {
175     AtomType atomType1 = a1.getAtomType();
176     AtomType atomType2 = a2.getAtomType();
177     int[] c = {atomType1.atomClass, atomType2.atomClass};
178     String key = BondType.sortKey(c);
179     StringBuilder sb = new StringBuilder(
180         format(" No BondType for key: %s\n %s -> %s\n %s -> %s", key,
181             a1, atomType1, a2, atomType2));
182     int c1 = atomType1.atomClass;
183     int c2 = atomType2.atomClass;
184     List<AtomType> types1 = forceField.getSimilarAtomTypes(atomType1);
185     List<AtomType> types2 = forceField.getSimilarAtomTypes(atomType2);
186     List<BondType> bondTypes = new ArrayList<>();
187     boolean match = false;
188     for (AtomType type1 : types1) {
189       for (AtomType type2 : types2) {
190         // Similar bond type must match at least one class.
191         if ((type1.atomClass != c1) && (type1.atomClass != c2) &&
192             (type2.atomClass != c1) && (type2.atomClass != c2)) {
193           continue;
194         }
195         BondType bondType = forceField.getBondType(type1, type2);
196         if (bondType != null && !bondTypes.contains(bondType)) {
197           if (!match) {
198             match = true;
199             sb.append("\n Similar Bond Types:");
200           }
201           bondTypes.add(bondType);
202           sb.append(format("\n  %s", bondType));
203         }
204       }
205     }
206 
207     logger.severe(sb.toString());
208   }
209 
210   /**
211    * {@inheritDoc}
212    */
213   @Override
214   public int compareTo(BondedTerm b) {
215     if (b == null) {
216       throw new NullPointerException();
217     }
218     if (b == this) {
219       return 0;
220     }
221     if (!b.getClass().isInstance(this)) {
222       return super.compareTo(b);
223     }
224     int this0 = atoms[0].getIndex();
225     int a0 = b.atoms[0].getIndex();
226     if (this0 < a0) {
227       return -1;
228     }
229     if (this0 > a0) {
230       return 1;
231     }
232     int this1 = atoms[1].getIndex();
233     int a1 = b.atoms[1].getIndex();
234 
235     return Integer.compare(this1, a1);
236   }
237 
238   /**
239    * {@inheritDoc}
240    *
241    * <p>Evaluate this Bond energy.
242    */
243   @Override
244   public double energy(boolean gradient, int threadID, AtomicDoubleArray3D grad, AtomicDoubleArray3D lambdaGrad) {
245     value = 0.0;
246     energy = 0.0;
247     // Only compute this term if at least one atom is being used.
248     if (!getUse()) {
249       return energy;
250     }
251     var atomA = atoms[0];
252     var atomB = atoms[1];
253     var va = atomA.getXYZ();
254     var vb = atomB.getXYZ();
255     var vab = va.sub(vb);
256     value = vab.length();
257     var prefactor = bondType.bondUnit * rigidScale * bondType.forceConstant;
258     // dv is deviation from ideal
259     var dv = value - bondType.distance;
260     if (bondType.bondFunction.hasFlatBottom()) {
261       if (dv > 0) {
262         dv = max(0, dv - bondType.flatBottomRadius);
263       } else if (dv < 0) {
264         dv = min(0, dv + bondType.flatBottomRadius);
265       } // Else, no adjustments needed.
266     }
267     var dv2 = dv * dv;
268     // A Taylor expansion of the Morse potential through the fourth power of the bond length deviation.
269     energy = prefactor * dv2 * (1.0 + bondType.cubic * dv + bondType.quartic * dv2);
270     if (gradient) {
271       // Compute the magnitude of the gradient.
272       var dedr = 2.0 * prefactor * dv * (1.0 + 1.5 * bondType.cubic * dv + 2.0 * bondType.quartic * dv2);
273       var de = 0.0;
274       if (value > 0.0) {
275         de = dedr / value;
276       }
277       var ga = vab.scale(de);
278       var ia = atomA.getIndex() - 1;
279       var ib = atomB.getIndex() - 1;
280       grad.add(threadID, ia, ga);
281       grad.sub(threadID, ib, ga);
282     }
283     value = dv;
284     return energy;
285   }
286 
287   /**
288    * Find the other Atom in <b>this</b> Bond. These two atoms are said to be 1-2.
289    *
290    * @param a The known Atom.
291    * @return The other Atom that makes up <b>this</b> Bond, or Null if Atom <code>a</code> is not
292    * part of
293    * <b>this</b> Bond.
294    */
295   public Atom get1_2(Atom a) {
296     if (a == atoms[0]) {
297       return atoms[1];
298     }
299     if (a == atoms[1]) {
300       return atoms[0];
301     }
302     return null; // Atom not found in bond
303   }
304 
305   /**
306    * Gets the current distance between the two Atoms in this Bond.
307    *
308    * @return Current distance.
309    */
310   public double getCurrentDistance() {
311     double[] x1 = new double[3];
312     x1 = atoms[0].getXYZ(x1);
313     double[] x2 = new double[3];
314     x2 = atoms[1].getXYZ(x2);
315     return DoubleMath.dist(x1, x2);
316   }
317 
318   /**
319    * Log details for this Bond energy term.
320    */
321   public void log() {
322     logger.info(
323         format(
324             " %-8s %6d-%s %6d-%s %6.4f  %6.4f  %10.4f",
325             "Bond",
326             atoms[0].getIndex(),
327             atoms[0].getAtomType().name,
328             atoms[1].getIndex(),
329             atoms[1].getAtomType().name,
330             bondType.distance,
331             value,
332             energy));
333   }
334 
335   /**
336    * {@inheritDoc}
337    */
338   @Override
339   public void removeFromParent() {
340     super.removeFromParent();
341     cy1 = null;
342     cy2 = null;
343     cy1tg = null;
344     cy2tg = null;
345     if (cy1t3d != null) {
346       RendererCache.poolTransform3D(cy1t3d);
347       RendererCache.poolTransform3D(cy2t3d);
348       cy1t3d = null;
349       cy2t3d = null;
350     }
351     if (branchGroup != null) {
352       branchGroup.detach();
353       branchGroup.setUserData(null);
354       RendererCache.poolDoubleCylinder(branchGroup);
355       branchGroup = null;
356     }
357   }
358 
359   /**
360    * Set a reference to the force field parameters.
361    *
362    * @param bondType a {@link ffx.potential.parameters.BondType} object.
363    */
364   public void setBondType(BondType bondType) {
365     this.bondType = bondType;
366   }
367 
368   /**
369    * Return the BondType for this Bond.
370    *
371    * @return Returns the BondType.
372    */
373   public BondType getBondType() {
374     return bondType;
375   }
376 
377   /**
378    * Set the color of this Bond's Java3D shapes based on the passed Atom.
379    *
380    * @param a Atom
381    */
382   public void setColor(Atom a) {
383     if (viewModel != ViewModel.INVISIBLE
384         && viewModel != ViewModel.WIREFRAME
385         && branchGroup != null) {
386       if (a == atoms[0]) {
387         cy1.setAppearance(a.getAtomAppearance());
388       } else if (a == atoms[1]) {
389         cy2.setAppearance(a.getAtomAppearance());
390       }
391     }
392     setWireVisible(wireVisible);
393   }
394 
395   /**
396    * Setter for the field <code>rigidScale</code>.
397    *
398    * @param rigidScale a double.
399    */
400   public void setRigidScale(double rigidScale) {
401     this.rigidScale = rigidScale;
402   }
403 
404   /**
405    * {@inheritDoc}
406    *
407    * <p>Polymorphic setView method.
408    */
409   @Override
410   public void setView(RendererCache.ViewModel newViewModel, List<BranchGroup> newShapes) {
411     switch (newViewModel) {
412       case WIREFRAME -> {
413         viewModel = ViewModel.WIREFRAME;
414         setWireVisible(true);
415         setCylinderVisible(false, newShapes);
416       }
417       case SPACEFILL, INVISIBLE, RMIN -> {
418         viewModel = ViewModel.INVISIBLE;
419         setWireVisible(false);
420         setCylinderVisible(false, newShapes);
421       }
422       case RESTRICT -> {
423         if (!atoms[0].isSelected() || !atoms[1].isSelected()) {
424           viewModel = ViewModel.INVISIBLE;
425           setWireVisible(false);
426           setCylinderVisible(false, newShapes);
427         }
428       }
429       case BALLANDSTICK, TUBE -> {
430         viewModel = newViewModel;
431         // Get the radius to use
432         double rad;
433         double len = getValue() / 2.0d;
434         if (viewModel == ViewModel.BALLANDSTICK) {
435           rad = 0.1d * RendererCache.radius;
436         } else {
437           rad = 0.2d * RendererCache.radius;
438         }
439         if (scale == null) {
440           scale = new Vector3d();
441         }
442         scale.set(rad, len, rad);
443         setWireVisible(false);
444         setCylinderVisible(true, newShapes);
445       }
446       case DETAIL -> {
447         int res = RendererCache.detail;
448         if (res != detail) {
449           detail = res;
450           if (branchGroup != null) {
451             Geometry geom1 = RendererCache.getCylinderGeom(0, detail);
452             Geometry geom2 = RendererCache.getCylinderGeom(1, detail);
453             Geometry geom3 = RendererCache.getCylinderGeom(2, detail);
454             cy1.removeAllGeometries();
455             cy2.removeAllGeometries();
456             cy1.addGeometry(geom1);
457             cy1.addGeometry(geom2);
458             cy1.addGeometry(geom3);
459             cy2.addGeometry(geom1);
460             cy2.addGeometry(geom2);
461             cy2.addGeometry(geom3);
462           }
463         }
464         if (scale == null) {
465           scale = new Vector3d();
466         }
467         double newRadius;
468         if (viewModel == ViewModel.BALLANDSTICK) {
469           newRadius = 0.1d * RendererCache.radius;
470         } else if (viewModel == ViewModel.TUBE) {
471           newRadius = 0.2d * RendererCache.radius;
472         } else {
473           break;
474         }
475         if (newRadius != scale.x) {
476           scale.x = newRadius;
477           scale.y = newRadius;
478           if (branchGroup != null) {
479             setView(viewModel, newShapes);
480           }
481         }
482       }
483       case SHOWHYDROGEN -> {
484         if (atoms[0].getAtomicNumber() == 1 || atoms[1].getAtomicNumber() == 1) {
485           setView(viewModel, newShapes);
486         }
487       }
488       case HIDEHYDROGEN -> {
489         if (atoms[0].getAtomicNumber() == 1 || atoms[1].getAtomicNumber() == 1) {
490           viewModel = ViewModel.INVISIBLE;
491           setWireVisible(false);
492           setCylinderVisible(false, newShapes);
493         }
494       }
495       case FILL, POINTS, LINES -> {
496         if (branchGroup != null && viewModel != ViewModel.INVISIBLE) {
497           cy1.setAppearance(atoms[0].getAtomAppearance());
498           cy2.setAppearance(atoms[1].getAtomAppearance());
499         }
500       }
501     }
502   }
503 
504   /**
505    * setWire
506    *
507    * @param l a {@link org.jogamp.java3d.LineArray} object.
508    * @param i The index of the LineArray to use.
509    */
510   public void setWire(LineArray l, int i) {
511     la = l;
512     lineIndex = i;
513   }
514 
515   /**
516    * {@inheritDoc}
517    *
518    * <p>Update recomputes bonds length, Wireframe vertices, and Cylinder Transforms
519    */
520   @Override
521   public void update() {
522     // Update the Bond Length
523     atoms[0].getXYZ(a13d);
524     atoms[1].getXYZ(a23d);
525     sub(a13d, a23d, diff3d);
526     double d = length(diff3d);
527     setValue(d);
528     DoubleMath.add(a13d, a23d, sum3d);
529     scale(sum3d, 0.5d, mid);
530     // Update the Wireframe Model.
531     if (la != null) {
532       for (int i = 0; i < 3; i++) {
533         coord[i] = a13d[i];
534         coord[3 + i] = mid[i];
535         coord[6 + i] = mid[i];
536         coord[9 + i] = a23d[i];
537       }
538       la.setCoordinates(lineIndex, coord);
539     }
540     // Update the Bond cylinder transforms.
541     if (branchGroup != null) {
542       normalize(diff3d, diff3d);
543       scale.y = d / 2.0d;
544       setBondTransform3d(cy1t3d, mid, diff3d, d, true);
545       scale(diff3d, -1.0d, diff3d);
546       setBondTransform3d(cy2t3d, mid, diff3d, d, false);
547       cy1tg.setTransform(cy1t3d);
548       cy2tg.setTransform(cy2t3d);
549     }
550   }
551 
552   /**
553    * Check to see if <b>this</b> Bond and another combine to form an angle
554    *
555    * @param b a {@link ffx.potential.bonded.Bond} object.
556    * @return True if Bond b helps form an angle with <b>this</b> Bond
557    */
558   boolean formsAngleWith(Bond b) {
559     for (Bond bond : formsAngleWith) {
560       if (b == bond) {
561         return true;
562       }
563     }
564     return false;
565   }
566 
567   /**
568    * Finds the common Atom between <b>this</b> Bond and Bond b.
569    *
570    * @param b Bond to compare with.
571    * @return The Atom the Bonds have in common or Null if they are the same Bond or have no atom in
572    * common
573    */
574   Atom getCommonAtom(Bond b) {
575     if (b == this || b == null) {
576       return null;
577     }
578     if (b.atoms[0] == atoms[0]) {
579       return atoms[0];
580     }
581     if (b.atoms[0] == atoms[1]) {
582       return atoms[1];
583     }
584     if (b.atoms[1] == atoms[0]) {
585       return atoms[0];
586     }
587     if (b.atoms[1] == atoms[1]) {
588       return atoms[1];
589     }
590     return null; // Common atom not found
591   }
592 
593   /**
594    * Find the Atom that <b>this</b> Bond and Bond b do not have in common.
595    *
596    * @param b Bond to compare with
597    * @return The Atom that Bond b and <b>this</b> Bond do not have in common, or Null if they have no
598    * Atom in common
599    */
600   Atom getOtherAtom(Bond b) {
601     if (b == this || b == null) {
602       return null;
603     }
604     if (b.atoms[0] == atoms[0]) {
605       return atoms[1];
606     }
607     if (b.atoms[0] == atoms[1]) {
608       return atoms[0];
609     }
610     if (b.atoms[1] == atoms[0]) {
611       return atoms[1];
612     }
613     if (b.atoms[1] == atoms[1]) {
614       return atoms[0];
615     }
616     return null;
617   }
618 
619   /**
620    * sameGroup
621    *
622    * @return a boolean.
623    */
624   boolean sameGroup() {
625     return atoms[0].getParent() == atoms[1].getParent();
626   }
627 
628   /**
629    * Specifies <b>this</b> Bond helps form an angle with the given Bond
630    *
631    * @param b Bond that forms an angle with <b>this</b> Bond
632    */
633   void setAngleWith(Bond b) {
634     formsAngleWith.add(b);
635   }
636 
637   /**
638    * Create the Bond Scenegraph Objects.
639    *
640    * @param newShapes List
641    */
642   private void initJ3D(List<BranchGroup> newShapes) {
643     detail = RendererCache.detail;
644     branchGroup = RendererCache.doubleCylinderFactory(atoms[0], atoms[1], detail);
645     cy1tg = (TransformGroup) branchGroup.getChild(0);
646     cy2tg = (TransformGroup) branchGroup.getChild(1);
647     cy1 = (Shape3D) cy1tg.getChild(0);
648     cy2 = (Shape3D) cy2tg.getChild(0);
649     newShapes.add(branchGroup);
650     cy1t3d = RendererCache.transform3DFactory();
651     cy2t3d = RendererCache.transform3DFactory();
652     update();
653   }
654 
655   /**
656    * setBondTransform3d
657    *
658    * @param t3d    a {@link org.jogamp.java3d.Transform3D} object.
659    * @param pos    an array of double.
660    * @param orient an array of double.
661    * @param len    a double.
662    * @param newRot a boolean.
663    */
664   private void setBondTransform3d(
665       Transform3D t3d, double[] pos, double[] orient, double len, boolean newRot) {
666     // Bond Orientation
667     if (newRot) {
668       double angle = angle(orient, y);
669       X(y, orient, bcross);
670       bcross[3] = angle - Math.PI;
671       axisAngle.set(bcross);
672     }
673     // Scale the orientation vector to be a fourth the bond length
674     // and add it to the position vector of the first atom
675     scale(orient, len / 4.0d, cstart);
676     DoubleMath.add(cstart, pos, cstart);
677     pos3d.set(cstart);
678     t3d.setTranslation(pos3d);
679     t3d.setRotation(axisAngle);
680     t3d.setScale(scale);
681   }
682 
683   /**
684    * Manage cylinder visibility.
685    *
686    * @param visible   boolean
687    * @param newShapes List
688    */
689   private void setCylinderVisible(boolean visible, List<BranchGroup> newShapes) {
690     if (!visible) {
691       // Make this Bond invisible.
692       if (branchGroup != null) {
693         cy1.setPickable(false);
694         cy1.setAppearance(RendererCache.nullAp);
695         cy2.setPickable(false);
696         cy2.setAppearance(RendererCache.nullAp);
697         // branchGroup = null;
698       }
699     } else if (branchGroup == null) {
700       // Get Java3D primitives from the RendererCache
701       initJ3D(newShapes);
702     } else {
703       // Scale the cylinders to match the current ViewModel
704       cy1t3d.setScale(scale);
705       cy1tg.setTransform(cy1t3d);
706       cy2t3d.setScale(scale);
707       cy2tg.setTransform(cy2t3d);
708       cy1.setAppearance(atoms[0].getAtomAppearance());
709       cy2.setAppearance(atoms[1].getAtomAppearance());
710     }
711   }
712 
713   /**
714    * Manage wireframe visibility.
715    *
716    * @param visible a boolean.
717    */
718   private void setWireVisible(boolean visible) {
719     if (!visible) {
720       wireVisible = false;
721       la.setColors(lineIndex, a0col);
722     } else {
723       wireVisible = true;
724       float[] cols = f16;
725       float[] col1 = f4a;
726       float[] col2 = f4b;
727       atoms[0].getAtomColor().get(col1);
728       atoms[1].getAtomColor().get(col2);
729       for (int i = 0; i < 3; i++) {
730         cols[i] = col1[i];
731         cols[4 + i] = col1[i];
732         cols[8 + i] = col2[i];
733         cols[12 + i] = col2[i];
734       }
735       la.setColors(lineIndex, cols);
736     }
737   }
738 }