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