UNPKG

3dmol

Version:

JavaScript/TypeScript molecular visualization library

897 lines (754 loc) 144 kB
<!DOCTYPE html><html lang="en" style="font-size:16px"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="Author" content="David Koes & 3Dmoljs Contributors"><meta name="Description" content="A modern, object-oriented JavaScript library for visualizing molecular data"><title>Source: GLModel.ts</title><!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--><script src="scripts/third-party/hljs.js" defer="defer"></script><script src="scripts/third-party/hljs-line-num.js" defer="defer"></script><script src="scripts/third-party/popper.js" defer="defer"></script><script src="scripts/third-party/tippy.js" defer="defer"></script><script src="scripts/third-party/tocbot.min.js"></script><script>var baseURL="/",locationPathname="";baseURL=(locationPathname=document.location.pathname).substr(0,locationPathname.lastIndexOf("/")+1)</script><link rel="stylesheet" href="styles/clean-jsdoc-theme.min.css"><style>article ul li{list-style:disc}</style><svg aria-hidden="true" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="display:none"><defs><symbol id="copy-icon" viewbox="0 0 488.3 488.3"><g><path d="M314.25,85.4h-227c-21.3,0-38.6,17.3-38.6,38.6v325.7c0,21.3,17.3,38.6,38.6,38.6h227c21.3,0,38.6-17.3,38.6-38.6V124 C352.75,102.7,335.45,85.4,314.25,85.4z M325.75,449.6c0,6.4-5.2,11.6-11.6,11.6h-227c-6.4,0-11.6-5.2-11.6-11.6V124 c0-6.4,5.2-11.6,11.6-11.6h227c6.4,0,11.6,5.2,11.6,11.6V449.6z"/><path d="M401.05,0h-227c-21.3,0-38.6,17.3-38.6,38.6c0,7.5,6,13.5,13.5,13.5s13.5-6,13.5-13.5c0-6.4,5.2-11.6,11.6-11.6h227 c6.4,0,11.6,5.2,11.6,11.6v325.7c0,6.4-5.2,11.6-11.6,11.6c-7.5,0-13.5,6-13.5,13.5s6,13.5,13.5,13.5c21.3,0,38.6-17.3,38.6-38.6 V38.6C439.65,17.3,422.35,0,401.05,0z"/></g></symbol><symbol id="search-icon" viewBox="0 0 512 512"><g><g><path d="M225.474,0C101.151,0,0,101.151,0,225.474c0,124.33,101.151,225.474,225.474,225.474 c124.33,0,225.474-101.144,225.474-225.474C450.948,101.151,349.804,0,225.474,0z M225.474,409.323 c-101.373,0-183.848-82.475-183.848-183.848S124.101,41.626,225.474,41.626s183.848,82.475,183.848,183.848 S326.847,409.323,225.474,409.323z"/></g></g><g><g><path d="M505.902,476.472L386.574,357.144c-8.131-8.131-21.299-8.131-29.43,0c-8.131,8.124-8.131,21.306,0,29.43l119.328,119.328 c4.065,4.065,9.387,6.098,14.715,6.098c5.321,0,10.649-2.033,14.715-6.098C514.033,497.778,514.033,484.596,505.902,476.472z"/></g></g></symbol><symbol id="font-size-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.246 15H4.754l-2 5H.6L7 4h2l6.4 16h-2.154l-2-5zm-.8-2L8 6.885 5.554 13h4.892zM21 12.535V12h2v8h-2v-.535a4 4 0 1 1 0-6.93zM19 18a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></symbol><symbol id="add-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"/></symbol><symbol id="minus-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5 11h14v2H5z"/></symbol><symbol id="dark-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2h.1A6.979 6.979 0 0 0 10 7zm-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938 7.999 7.999 0 0 0 4 12z"/></symbol><symbol id="light-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85l1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></symbol><symbol id="reset-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M18.537 19.567A9.961 9.961 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10c0 2.136-.67 4.116-1.81 5.74L17 12h3a8 8 0 1 0-2.46 5.772l.997 1.795z"/></symbol><symbol id="down-icon" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.7803 6.21967C13.0732 6.51256 13.0732 6.98744 12.7803 7.28033L8.53033 11.5303C8.23744 11.8232 7.76256 11.8232 7.46967 11.5303L3.21967 7.28033C2.92678 6.98744 2.92678 6.51256 3.21967 6.21967C3.51256 5.92678 3.98744 5.92678 4.28033 6.21967L8 9.93934L11.7197 6.21967C12.0126 5.92678 12.4874 5.92678 12.7803 6.21967Z"></path></symbol><symbol id="codepen-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M16.5 13.202L13 15.535v3.596L19.197 15 16.5 13.202zM14.697 12L12 10.202 9.303 12 12 13.798 14.697 12zM20 10.869L18.303 12 20 13.131V10.87zM19.197 9L13 4.869v3.596l3.5 2.333L19.197 9zM7.5 10.798L11 8.465V4.869L4.803 9 7.5 10.798zM4.803 15L11 19.131v-3.596l-3.5-2.333L4.803 15zM4 13.131L5.697 12 4 10.869v2.262zM2 9a1 1 0 0 1 .445-.832l9-6a1 1 0 0 1 1.11 0l9 6A1 1 0 0 1 22 9v6a1 1 0 0 1-.445.832l-9 6a1 1 0 0 1-1.11 0l-9-6A1 1 0 0 1 2 15V9z"/></symbol><symbol id="close-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"/></symbol><symbol id="menu-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M3 4h18v2H3V4zm0 7h18v2H3v-2zm0 7h18v2H3v-2z"/></symbol></defs></svg></head><body data-theme="light"><div class="sidebar-container"><div class="sidebar" id="sidebar"><a href="/" class="sidebar-title sidebar-title-anchor">3Dmol.js</a><div class="sidebar-items-container"><div class="sidebar-section-title with-arrow" data-isopen="false" id="mei5HJjYKLn6aua5Z5PM5"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="$3Dmol.Label.html">Label</a></div><div class="sidebar-section-children"><a href="$3Dmol.StateManager.html">StateManager</a></div><div class="sidebar-section-children"><a href="$3Dmol.UI.html">UI</a></div><div class="sidebar-section-children"><a href="Color.html">Color</a></div><div class="sidebar-section-children"><a href="CustomLinear.html">CustomLinear</a></div><div class="sidebar-section-children"><a href="Cylinder.html">Cylinder</a></div><div class="sidebar-section-children"><a href="GLModel.html">GLModel</a></div><div class="sidebar-section-children"><a href="GLShape.html">GLShape</a></div><div class="sidebar-section-children"><a href="GLShape_GLShape.html">GLShape</a></div><div class="sidebar-section-children"><a href="GLViewer.html">GLViewer</a></div><div class="sidebar-section-children"><a href="GLVolumetricRender.html">GLVolumetricRender</a></div><div class="sidebar-section-children"><a href="Matrix3.html">Matrix3</a></div><div class="sidebar-section-children"><a href="Matrix4.html">Matrix4</a></div><div class="sidebar-section-children"><a href="Quaternion.html">Quaternion</a></div><div class="sidebar-section-children"><a href="ROYGB.html">ROYGB</a></div><div class="sidebar-section-children"><a href="RWB.html">RWB</a></div><div class="sidebar-section-children"><a href="Ray.html">Ray</a></div><div class="sidebar-section-children"><a href="Sinebow.html">Sinebow</a></div><div class="sidebar-section-children"><a href="Sphere.html">Sphere</a></div><div class="sidebar-section-children"><a href="Surface.html">Surface</a></div><div class="sidebar-section-children"><a href="Triangle.html">Triangle</a></div><div class="sidebar-section-children"><a href="Vector2.html">Vector2</a></div><div class="sidebar-section-children"><a href="Vector3.html">Vector3</a></div><div class="sidebar-section-children"><a href="VolumeData.html">VolumeData</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="kv4Yx5z0THf_b8XR4Gje1"><div>Namespaces</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="GLDraw.html">GLDraw</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="ykThSufQWJcKGT1kXzr-A"><div>Tutorials</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="tutorial-code.html">Using 3Dmol within your code</a></div><div class="sidebar-section-children"><a href="tutorial-embeddable.html">Embedding a 3Dmol Viewer</a></div><div class="sidebar-section-children"><a href="tutorial-home.html">3Dmol Tutorials - Home</a></div><div class="sidebar-section-children"><a href="tutorial-learning_environment.html">Active Learning with 3Dmol.js</a></div><div class="sidebar-section-children"><a href="tutorial-url.html">Viewing Molecules with the 3Dmol Server</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="XcP4RUEg6tp7YKHbBVi9E"><div>Interfaces</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="AmbientOcclusionStyle.html">AmbientOcclusionStyle</a></div><div class="sidebar-section-children"><a href="ArrowSpec.html">ArrowSpec</a></div><div class="sidebar-section-children"><a href="AtomSelectionSpec.html">AtomSelectionSpec</a></div><div class="sidebar-section-children"><a href="AtomSpec.html">AtomSpec</a></div><div class="sidebar-section-children"><a href="AtomStyleSpec.html">AtomStyleSpec</a></div><div class="sidebar-section-children"><a href="BondStyle.html">BondStyle</a></div><div class="sidebar-section-children"><a href="BoxSpec.html">BoxSpec</a></div><div class="sidebar-section-children"><a href="CartoonStyleSpec.html">CartoonStyleSpec</a></div><div class="sidebar-section-children"><a href="ClickSphereStyleSpec.html">ClickSphereStyleSpec</a></div><div class="sidebar-section-children"><a href="CrossStyleSpec.html">CrossStyleSpec</a></div><div class="sidebar-section-children"><a href="CurveSpec.html">CurveSpec</a></div><div class="sidebar-section-children"><a href="CustomShapeSpec.html">CustomShapeSpec</a></div><div class="sidebar-section-children"><a href="CylinderSpec.html">CylinderSpec</a></div><div class="sidebar-section-children"><a href="DashedBondSpec.html">DashedBondSpec</a></div><div class="sidebar-section-children"><a href="FogSpec.html">FogSpec</a></div><div class="sidebar-section-children"><a href="IsoSurfaceSpec.html">IsoSurfaceSpec</a></div><div class="sidebar-section-children"><a href="LabelSpec.html">LabelSpec</a></div><div class="sidebar-section-children"><a href="LineSpec.html">LineSpec</a></div><div class="sidebar-section-children"><a href="LineStyleSpec.html">LineStyleSpec</a></div><div class="sidebar-section-children"><a href="OutlineStyle.html">OutlineStyle</a></div><div class="sidebar-section-children"><a href="ParserOptionsSpec.html">ParserOptionsSpec</a></div><div class="sidebar-section-children"><a href="ShapeSpec.html">ShapeSpec</a></div><div class="sidebar-section-children"><a href="SphereSpec.html">SphereSpec</a></div><div class="sidebar-section-children"><a href="SphereStyleSpec.html">SphereStyleSpec</a></div><div class="sidebar-section-children"><a href="StickStyleSpec.html">StickStyleSpec</a></div><div class="sidebar-section-children"><a href="SurfaceStyleSpec.html">SurfaceStyleSpec</a></div><div class="sidebar-section-children"><a href="UnitCellStyleSpec.html">UnitCellStyleSpec</a></div><div class="sidebar-section-children"><a href="ViewStyle.html">ViewStyle</a></div><div class="sidebar-section-children"><a href="ViewerGridSpec.html">ViewerGridSpec</a></div><div class="sidebar-section-children"><a href="ViewerSpec.html">ViewerSpec</a></div><div class="sidebar-section-children"><a href="VolumetricRendererSpec.html">VolumetricRendererSpec</a></div><div class="sidebar-section-children"><a href="WithinSelectionSpec.html">WithinSelectionSpec</a></div><div class="sidebar-section-children"><a href="global.html#XYZ">XYZ</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="jmKnIXRJjdjZ366ci7cdd"><div>Global</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="global.html#BCIF">BCIF</a></div><div class="sidebar-section-children"><a href="global.html#CAP">CAP</a></div><div class="sidebar-section-children"><a href="global.html#CDJSON">CDJSON</a></div><div class="sidebar-section-children"><a href="global.html#CIF">CIF</a></div><div class="sidebar-section-children"><a href="global.html#CUBE">CUBE</a></div><div class="sidebar-section-children"><a href="global.html#ColorSpec">ColorSpec</a></div><div class="sidebar-section-children"><a href="global.html#ColorschemeSpec">ColorschemeSpec</a></div><div class="sidebar-section-children"><a href="global.html#GRO">GRO</a></div><div class="sidebar-section-children"><a href="global.html#GradientSpec">GradientSpec</a></div><div class="sidebar-section-children"><a href="global.html#MMTFparser">MMTFparser</a></div><div class="sidebar-section-children"><a href="global.html#MOL2">MOL2</a></div><div class="sidebar-section-children"><a href="global.html#OFFSETS">OFFSETS</a></div><div class="sidebar-section-children"><a href="global.html#PDB">PDB</a></div><div class="sidebar-section-children"><a href="global.html#PQR">PQR</a></div><div class="sidebar-section-children"><a href="global.html#PRMTOP">PRMTOP</a></div><div class="sidebar-section-children"><a href="global.html#SDF">SDF</a></div><div class="sidebar-section-children"><a href="global.html#SurfaceType">SurfaceType</a></div><div class="sidebar-section-children"><a href="global.html#VASP">VASP</a></div><div class="sidebar-section-children"><a href="global.html#XYZ">XYZ</a></div><div class="sidebar-section-children"><a href="global.html#assignPDBBonds">assignPDBBonds</a></div><div class="sidebar-section-children"><a href="global.html#builtinColorSchemes">builtinColorSchemes</a></div><div class="sidebar-section-children"><a href="global.html#builtinGradients">builtinGradients</a></div><div class="sidebar-section-children"><a href="global.html#conversionMatrix3">conversionMatrix3</a></div><div class="sidebar-section-children"><a href="global.html#createViewer">createViewer</a></div><div class="sidebar-section-children"><a href="global.html#createViewerGrid">createViewerGrid</a></div><div class="sidebar-section-children"><a href="global.html#decode">decode</a></div><div class="sidebar-section-children"><a href="global.html#dic">dic</a></div><div class="sidebar-section-children"><a href="global.html#download">download</a></div><div class="sidebar-section-children"><a href="global.html#elementColors">elementColors</a></div><div class="sidebar-section-children"><a href="global.html#get">get</a></div><div class="sidebar-section-children"><a href="global.html#getColorFromStyle">getColorFromStyle</a></div><div class="sidebar-section-children"><a href="global.html#getbin">getbin</a></div><div class="sidebar-section-children"><a href="global.html#parseV3000">parseV3000</a></div><div class="sidebar-section-children"><a href="global.html#setSyncSurface">setSyncSurface</a></div><div class="sidebar-section-children"><a href="global.html#ssColors">ssColors</a></div><div class="sidebar-section-children"><a href="global.html#syncSurface">syncSurface</a></div><div class="sidebar-section-children"><a href="global.html#viewers">viewers</a></div></div></div></div></div><div class="navbar-container" id="VuAckcnZhf"><nav class="navbar"><div class="navbar-left-items"><div class="navbar-item"><a id="" href="https://3dmol.org/doc/index.html" target="">Documentation</a></div><div class="navbar-item"><a id="" href="tutorial-home.html" target="">Tutorials</a></div><div class="navbar-item"><a id="" href="https://github.com/3dmol/3Dmol.js" target="_blank">GitHub</a></div></div><div class="navbar-right-items"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#dark-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div><nav></nav></nav></div><div class="toc-container"><div class="toc-content"><span class="bold">On this page</span><div id="eed4d2a0bfd64539bb9df78095dec881"></div></div></div><div class="body-wrapper"><div class="main-content"><div class="main-wrapper"><section id="source-page" class="source-page"><header><h1 id="title" class="has-anchor">GLModel.ts</h1></header><article><pre class="prettyprint source lang-js"><code>// A model is a collection of related atoms. Bonds are only allowed between //atoms in the same model. An atom is uniquely specified by its model id and //its serial number. //A glmodel knows how to apply the styles on each atom to create a gl object import { Geometry, Material, StickImposterMaterial } from "./WebGL"; import { Sphere, Cylinder } from "./WebGL/shapes"; import { Vector3, Matrix4, conversionMatrix3, Matrix3, XYZ } from "./WebGL/math"; import { Color, CC, ColorschemeSpec, ColorSpec } from "./colors"; import { InstancedMaterial, SphereImposterMaterial, MeshLambertMaterial, Object3D, Mesh, LineBasicMaterial, Line, LineStyle } from "./WebGL"; import { CAP, GLDraw } from "./GLDraw" import { CartoonStyleSpec, drawCartoon } from "./glcartoon"; import { elementColors } from "./colors"; import { get, deepCopy, extend, getExtent, getAtomProperty, makeFunction, getPropertyRange, specStringToObject, getbin, getColorFromStyle, inflateString } from "./utilities"; import { Gradient } from "./Gradient"; import { Parsers } from "./parsers"; import { NetCDFReader } from "netcdfjs" import { AtomSelectionSpec, AtomSpec } from "./specs"; import { GLViewer } from "GLViewer"; import { ArrowSpec } from "GLShape"; import { ParserOptionsSpec } from "./parsers/ParserOptionsSpec"; import { LabelSpec } from "Label"; import { assignBonds } from "./parsers/utils/assignBonds"; /** * GLModel represents a group of related atoms * @class */ export class GLModel { // class variables go here static defaultAtomStyle: AtomStyleSpec = { line: {} }; static defaultlineWidth = 1.0; // Reference: A. Bondi, J. Phys. Chem., 1964, 68, 441. // https://en.wikipedia.org/wiki/Van_der_Waals_radius static vdwRadii = { "H": 1.2, "He": 1.4, "Li": 1.82, "Be": 1.53, "B": 1.92, "C": 1.7, "N": 1.55, "O": 1.52, "F": 1.47, "Ne": 1.54, "Na": 2.27, "Mg": 1.73, "Al": 1.84, "Si": 2.1, "P": 1.8, "S": 1.8, "Cl": 1.75, "Ar": 1.88, "K": 2.75, "Ca": 2.31, "Ni": 1.63, "Cu": 1.4, "Zn": 1.39, "Ga": 1.87, "Ge": 2.11, "As": 1.85, "Se": 1.9, "Br": 1.85, "Kr": 2.02, "Rb": 3.03, "Sr": 2.49, "Pd": 1.63, "Ag": 1.72, "Cd": 1.58, "In": 1.93, "Sn": 2.17, "Sb": 2.06, "Te": 2.06, "I": 1.98, "Xe": 2.16, "Cs": 3.43, "Ba": 2.68, "Pt": 1.75, "Au": 1.66, "Hg": 1.55, "Tl": 1.96, "Pb": 2.02, "Bi": 2.07, "Po": 1.97, "At": 2.02, "Rn": 2.20, "Fr": 3.48, "Ra": 2.83, "U": 1.86 }; // class functions // return true if a and b represent the same style static sameObj(a, b) { if (a &amp;&amp; b) return JSON.stringify(a) == JSON.stringify(b); else return a == b; }; public unitCellObjects: any; // private variables private atoms: AtomSpec[] = []; private frames: any = []; private box: any = null; private atomdfs: any = null; //depth first search over connected components private id = 0; private hidden: any = false; private molObj: any = null; private renderedMolObj: any = null; private lastColors: any = null; private modelData: any = {}; private modelDatas: any = null; //if there is different modelData per frame private idMatrix = new Matrix4(); private dontDuplicateAtoms = true; private defaultColor = elementColors.defaultColor; private options: any; private ElementColors: any; private viewer: GLViewer; private readonly defaultSphereRadius: number; private readonly defaultCartoonQuality: number; // bonds as cylinders private readonly defaultStickRadius = 0.25; constructor(mid, options?, viewer?) { this.options = options || {}; this.viewer = viewer; this.ElementColors = (this.options.defaultcolors) ? this.options.defaultcolors : elementColors.defaultColors; this.defaultSphereRadius = (this.options.defaultSphereRadius) ? this.options.defaultSphereRadius : 1.5; this.defaultCartoonQuality = (this.options.cartoonQuality) ? this.options.cartoonQuality : 10; this.id = mid; } // return proper radius for atom given style /** * * @param {AtomSpec} atom * @param {atomstyle} style * @return {number} * */ private getRadiusFromStyle(atom: AtomSpec, style: SphereStyleSpec | ClickSphereStyleSpec | CrossStyleSpec) { var r = this.defaultSphereRadius; if (typeof (style.radius) != "undefined") r = style.radius; else if (GLModel.vdwRadii[atom.elem]) r = GLModel.vdwRadii[atom.elem]; else if (atom.elem.length > 1) { //see if adjusting case helps let e: string = atom.elem; e = e[0].toUpperCase() + e[1].toLowerCase(); if (GLModel.vdwRadii[e]) r = GLModel.vdwRadii[e]; } if (typeof (style.scale) != "undefined") r *= style.scale; return r; }; // cross drawing /** * * @param {AtomSpec} atom * @param {Record&lt;number, Geometry>} geos */ private drawAtomCross(atom: AtomSpec, geos: Record&lt;number, Geometry>) { if (!atom.style.cross) return; var style = atom.style.cross; if (style.hidden) return; var linewidth = (style.linewidth || GLModel.defaultlineWidth); if (!geos[linewidth]) geos[linewidth] = new Geometry(); var geoGroup = geos[linewidth].updateGeoGroup(6); var delta = this.getRadiusFromStyle(atom, style); var points = [[delta, 0, 0], [-delta, 0, 0], [0, delta, 0], [0, -delta, 0], [0, 0, delta], [0, 0, -delta]]; var clickable = atom.clickable || atom.hoverable; if (clickable &amp;&amp; atom.intersectionShape === undefined) atom.intersectionShape = { sphere: [], cylinder: [], line: [] }; var c = getColorFromStyle(atom, style); var vertexArray = geoGroup.vertexArray; var colorArray = geoGroup.colorArray; for (var j = 0; j &lt; 6; j++) { var offset = geoGroup.vertices * 3; geoGroup.vertices++; vertexArray[offset] = atom.x + points[j][0]; vertexArray[offset + 1] = atom.y + points[j][1]; vertexArray[offset + 2] = atom.z + points[j][2]; colorArray[offset] = c.r; colorArray[offset + 1] = c.g; colorArray[offset + 2] = c.b; if (clickable) { var point = new Vector3(points[j][0], points[j][1], points[j][2]); //decrease cross size for selection to prevent misselection from atom overlap point.multiplyScalar(0.1); point.set(point.x + atom.x, point.y + atom.y, point.z + atom.z); atom.intersectionShape.line.push(point); } } }; private getGoodCross(atom: AtomSpec, atom2: AtomSpec, p1, dir) { // get vector 2 different neighboring atom //find most divergent neighbor var bestv = null; var bestlen = -1; for (var j = 0, n = atom.bonds.length; j &lt; n; j++) { if (atom.bonds[j] != atom2.index) { let j2 = atom.bonds[j]; let atom3 = this.atoms[j2]; let p3 = new Vector3(atom3.x, atom3.y, atom3.z); let dir2 = p3.clone(); dir2.sub(p1); let v = dir2.clone(); v.cross(dir); var l = v.lengthSq(); if (l > bestlen) { bestlen = l; bestv = v; if (bestlen > 0.1) { return bestv; } } } } return bestv; }; //from atom, return a normalized vector v that is orthogonal and along which //it is appropraite to draw multiple bonds private getSideBondV(atom: AtomSpec, atom2: AtomSpec, i: number) { var i2, j2, atom3, p3, dir2; var p1 = new Vector3(atom.x, atom.y, atom.z); var p2 = new Vector3(atom2.x, atom2.y, atom2.z); var dir = p2.clone(); var v = null; dir.sub(p1); if (atom.bonds.length === 1) { if (atom2.bonds.length === 1) { v = dir.clone(); if (Math.abs(v.x) > 0.0001) v.y += 1; else v.x += 1; } else { i2 = (i + 1) % atom2.bonds.length; j2 = atom2.bonds[i2]; atom3 = this.atoms[j2]; if (atom3.index == atom.index) { // get distinct atom i2 = (i2 + 1) % atom2.bonds.length; j2 = atom2.bonds[i2]; atom3 = this.atoms[j2]; } p3 = new Vector3(atom3.x, atom3.y, atom3.z); dir2 = p3.clone(); dir2.sub(p1); v = dir2.clone(); v.cross(dir); } } else { v = this.getGoodCross(atom, atom2, p1, dir); if (v.lengthSq() &lt; 0.01) { var v2 = this.getGoodCross(atom2, atom, p1, dir); if (v2 != null) v = v2; //can be null if no other neighbors } } // especially for C#C (triple bond) dir and dir2 // may be opposites resulting in a zero v if (v.lengthSq() &lt; 0.01) { v = dir.clone(); if (Math.abs(v.x) > 0.0001) v.y += 1; else v.x += 1; } v.cross(dir); v.normalize(); return v; }; private addLine(vertexArray, colorArray, offset, p1: Vector3, p2: Vector3, c1: Color) { //make line from p1 to p2, does not incremeant counts vertexArray[offset] = p1.x; vertexArray[offset + 1] = p1.y; vertexArray[offset + 2] = p1.z; colorArray[offset] = c1.r; colorArray[offset + 1] = c1.g; colorArray[offset + 2] = c1.b; vertexArray[offset + 3] = p2.x; vertexArray[offset + 4] = p2.y; vertexArray[offset + 5] = p2.z; colorArray[offset + 3] = c1.r; colorArray[offset + 4] = c1.g; colorArray[offset + 5] = c1.b; }; // bonds - both atoms must match bond style // standardize on only drawing for lowest to highest /** * * @param {AtomSpec} * atom * @param {AtomSpec[]} atoms * @param {Record&lt;number,Geometry>} geos */ private drawBondLines(atom: AtomSpec, atoms: AtomSpec[], geos: Record&lt;number, Geometry>) { if (!atom.style.line) return; var style = atom.style.line; if (style.hidden) return; var p1a, p1b, p2a, p2b; // have a separate geometry for each linewidth var linewidth = (style.linewidth || GLModel.defaultlineWidth); if (!geos[linewidth]) geos[linewidth] = new Geometry(); /** @type {geometryGroup} */ var geoGroup = geos[linewidth].updateGeoGroup(6 * atom.bonds.length); //reserve enough space even for triple bonds var vertexArray = geoGroup.vertexArray; var colorArray = geoGroup.colorArray; for (var i = 0; i &lt; atom.bonds.length; i++) { var j = atom.bonds[i]; // our neighbor var atom2 = atoms[j]; if (!atom2.style.line) continue; // don't sweat the details if (atom.index >= atom2.index) // only draw if less, this way we can do multi bonds correctly continue; var p1 = new Vector3(atom.x, atom.y, atom.z); var p2 = new Vector3(atom2.x, atom2.y, atom2.z); var mp = p1.clone().add(p2).multiplyScalar(0.5); var singleBond = false; var atomneedsi = atom.clickable || atom.hoverable; var atom2needsi = atom2.clickable || atom2.hoverable; if (atomneedsi || atom2needsi) { if (atomneedsi) { if (atom.intersectionShape === undefined) atom.intersectionShape = { sphere: [], cylinder: [], line: [], triangle: [] }; atom.intersectionShape.line.push(p1); atom.intersectionShape.line.push(mp); } if (atom2needsi) { if (atom2.intersectionShape === undefined) atom2.intersectionShape = { sphere: [], cylinder: [], line: [], triangle: [] }; atom2.intersectionShape.line.push(mp); atom2.intersectionShape.line.push(p2); } } var c1 = getColorFromStyle(atom, atom.style.line); var c2 = getColorFromStyle(atom2, atom2.style.line); if (atom.bondStyles &amp;&amp; atom.bondStyles[i]) { var bstyle = atom.bondStyles[i]; if (!bstyle.iswire) { continue; } if (bstyle.singleBond) singleBond = true; if (typeof (bstyle.color1) != "undefined") { c1 = CC.color(bstyle.color1) as Color; } if (typeof (bstyle.color2) != "undefined") { c2 = CC.color(bstyle.color2) as Color; } } var offset = geoGroup.vertices * 3; var mpa, mpb; if (atom.bondOrder[i] > 1 &amp;&amp; atom.bondOrder[i] &lt; 4 &amp;&amp; !singleBond) { var v = this.getSideBondV(atom, atom2, i); var dir = p2.clone(); dir.sub(p1); if (atom.bondOrder[i] == 2) { //double v.multiplyScalar(0.1); p1a = p1.clone(); p1a.add(v); p1b = p1.clone(); p1b.sub(v); p2a = p1a.clone(); p2a.add(dir); p2b = p1b.clone(); p2b.add(dir); if (c1 == c2) { geoGroup.vertices += 4; this.addLine(vertexArray, colorArray, offset, p1a, p2a, c1); this.addLine(vertexArray, colorArray, offset + 6, p1b, p2b, c1); } else { geoGroup.vertices += 8; dir.multiplyScalar(0.5); mpa = p1a.clone(); mpa.add(dir); mpb = p1b.clone(); mpb.add(dir); this.addLine(vertexArray, colorArray, offset, p1a, mpa, c1); this.addLine(vertexArray, colorArray, offset + 6, mpa, p2a, c2); this.addLine(vertexArray, colorArray, offset + 12, p1b, mpb, c1); this.addLine(vertexArray, colorArray, offset + 18, mpb, p2b, c2); } } else if (atom.bondOrder[i] == 3) { //triple v.multiplyScalar(0.1); p1a = p1.clone(); p1a.add(v); p1b = p1.clone(); p1b.sub(v); p2a = p1a.clone(); p2a.add(dir); p2b = p1b.clone(); p2b.add(dir); if (c1 == c2) { geoGroup.vertices += 6; this.addLine(vertexArray, colorArray, offset, p1, p2, c1); this.addLine(vertexArray, colorArray, offset + 6, p1a, p2a, c1); this.addLine(vertexArray, colorArray, offset + 12, p1b, p2b, c1); } else { geoGroup.vertices += 12; dir.multiplyScalar(0.5); mpa = p1a.clone(); mpa.add(dir); mpb = p1b.clone(); mpb.add(dir); this.addLine(vertexArray, colorArray, offset, p1, mp, c1); this.addLine(vertexArray, colorArray, offset + 6, mp, p2, c2); this.addLine(vertexArray, colorArray, offset + 12, p1a, mpa, c1); this.addLine(vertexArray, colorArray, offset + 18, mpa, p2a, c2); this.addLine(vertexArray, colorArray, offset + 24, p1b, mpb, c1); this.addLine(vertexArray, colorArray, offset + 30, mpb, p2b, c2); } } } else { //single bond if (c1 == c2) { geoGroup.vertices += 2; this.addLine(vertexArray, colorArray, offset, p1, p2, c1); } else { geoGroup.vertices += 4; this.addLine(vertexArray, colorArray, offset, p1, mp, c1); this.addLine(vertexArray, colorArray, offset + 6, mp, p2, c2); } } } }; //sphere drawing //See also: drawCylinder /** * * @param {AtomSpec} atom * @param {Geometry} geo */ private drawAtomSphere(atom: AtomSpec, geo: Geometry) { if (!atom.style.sphere) return; var style = atom.style.sphere; if (style.hidden) return; var C = getColorFromStyle(atom, style); var radius = this.getRadiusFromStyle(atom, style); if ((atom.clickable === true || atom.hoverable) &amp;&amp; (atom.intersectionShape !== undefined)) { var center = new Vector3(atom.x, atom.y, atom.z); atom.intersectionShape.sphere.push(new Sphere(center, radius)); } GLDraw.drawSphere(geo, atom, radius, C); }; /** Register atom shaped click handlers */ private drawAtomClickSphere(atom: AtomSpec) { if (!atom.style.clicksphere) return; var style = atom.style.clicksphere; if (style.hidden) return; var radius = this.getRadiusFromStyle(atom, style); if ((atom.clickable === true || atom.hoverable) &amp;&amp; (atom.intersectionShape !== undefined)) { var center = new Vector3(atom.x, atom.y, atom.z); atom.intersectionShape.sphere.push(new Sphere(center, radius)); } }; private drawAtomInstanced(atom: AtomSpec, geo: Geometry) { if (!atom.style.sphere) return; var style = atom.style.sphere; if (style.hidden) return; var radius = this.getRadiusFromStyle(atom, style); var C = getColorFromStyle(atom, style); var geoGroup = geo.updateGeoGroup(1); var startv = geoGroup.vertices; var start = startv * 3; var vertexArray = geoGroup.vertexArray; var colorArray = geoGroup.colorArray; var radiusArray = geoGroup.radiusArray; vertexArray[start] = atom.x; vertexArray[start + 1] = atom.y; vertexArray[start + 2] = atom.z; colorArray[start] = C.r; colorArray[start + 1] = C.g; colorArray[start + 2] = C.b; radiusArray[startv] = radius; if ((atom.clickable === true || atom.hoverable) &amp;&amp; (atom.intersectionShape !== undefined)) { var center = new Vector3(atom.x, atom.y, atom.z); atom.intersectionShape.sphere.push(new Sphere(center, radius)); } geoGroup.vertices += 1; }; private drawSphereImposter(geo: Geometry, center: XYZ, radius: number, C: Color) { //create flat square var geoGroup = geo.updateGeoGroup(4); var i; var startv = geoGroup.vertices; var start = startv * 3; var vertexArray = geoGroup.vertexArray; var colorArray = geoGroup.colorArray; //use center point for each vertex for (i = 0; i &lt; 4; i++) { vertexArray[start + 3 * i] = center.x; vertexArray[start + 3 * i + 1] = center.y; vertexArray[start + 3 * i + 2] = center.z; } //same colors for all 4 vertices var normalArray = geoGroup.normalArray; for (i = 0; i &lt; 4; i++) { colorArray[start + 3 * i] = C.r; colorArray[start + 3 * i + 1] = C.g; colorArray[start + 3 * i + 2] = C.b; } normalArray[start + 0] = -radius; normalArray[start + 1] = radius; normalArray[start + 2] = 0; normalArray[start + 3] = -radius; normalArray[start + 4] = -radius; normalArray[start + 5] = 0; normalArray[start + 6] = radius; normalArray[start + 7] = -radius; normalArray[start + 8] = 0; normalArray[start + 9] = radius; normalArray[start + 10] = radius; normalArray[start + 11] = 0; geoGroup.vertices += 4; //two faces var faceArray = geoGroup.faceArray; var faceoffset = geoGroup.faceidx; //not number faces, but index faceArray[faceoffset + 0] = startv; faceArray[faceoffset + 1] = startv + 1; faceArray[faceoffset + 2] = startv + 2; faceArray[faceoffset + 3] = startv + 2; faceArray[faceoffset + 4] = startv + 3; faceArray[faceoffset + 5] = startv; geoGroup.faceidx += 6; }; //dkoes - code for sphere imposters private drawAtomImposter(atom: AtomSpec, geo: Geometry) { if (!atom.style.sphere) return; var style = atom.style.sphere; if (style.hidden) return; var radius = this.getRadiusFromStyle(atom, style); var C = getColorFromStyle(atom, style); if ((atom.clickable === true || atom.hoverable) &amp;&amp; (atom.intersectionShape !== undefined)) { var center = new Vector3(atom.x, atom.y, atom.z); atom.intersectionShape.sphere.push(new Sphere(center, radius)); } this.drawSphereImposter(geo, atom as XYZ, radius, C); }; private calculateDashes(from: XYZ, to: XYZ, radius: number, dashLength: number, gapLength: number) { // Calculate the length of a cylinder defined by two points 'from' and 'to'. var cylinderLength = Math.sqrt( Math.pow((from.x - to.x), 2) + Math.pow((from.y - to.y), 2) + Math.pow((from.z - to.z), 2) ); // Ensure non-negative values for radius, dashLength, and gapLength. // Adjust gapLength to include the radius of the cylinder. radius = Math.max(radius, 0); gapLength = Math.max(gapLength, 0) + 2 * radius; dashLength = Math.max(dashLength, 0.001); // Handle cases where the combined length of dash and gap exceeds the cylinder's length. // In such cases, use a single dash to represent the entire cylinder with no gaps. if (dashLength + gapLength > cylinderLength) { dashLength = cylinderLength; gapLength = 0; // No gap as the dash fills the entire cylinder. } // Calculate the total number of dash-gap segments that can fit within the cylinder. var totalSegments = Math.floor((cylinderLength - dashLength) / (dashLength + gapLength)) + 1; // Compute the total length covered by dashes. var totalDashLength = totalSegments * dashLength; // Recalculate gap length to evenly distribute remaining space among gaps. // This ensures dashes and gaps are evenly spaced within the cylinder. gapLength = (cylinderLength - totalDashLength) / totalSegments; var new_to; var new_from = new Vector3(from.x, from.y, from.z); var gapVector = new Vector3((to.x - from.x) / (cylinderLength / gapLength), (to.y - from.y) / (cylinderLength / gapLength), (to.z - from.z) / (cylinderLength / gapLength)); var dashVector = new Vector3((to.x - from.x) / (cylinderLength / dashLength), (to.y - from.y) / (cylinderLength / dashLength), (to.z - from.z) / (cylinderLength / dashLength)); var segments = []; for (var place = 0; place &lt; totalSegments; place++) { new_to = new Vector3(new_from.x + dashVector.x, new_from.y + dashVector.y, new_from.z + dashVector.z); segments.push({ from: new_from, to: new_to }); new_from = new Vector3(new_to.x + gapVector.x, new_to.y + gapVector.y, new_to.z + gapVector.z); } return segments; } static drawStickImposter(geo: Geometry, from: XYZ, to: XYZ, radius: number, color: Color, fromCap: CAP = 0, toCap: CAP = 0) { //we need the four corners - two have from coord, two have to coord, the normal //is the opposing point, from which we can get the normal and length //also need the radius var geoGroup = geo.updateGeoGroup(4); var startv = geoGroup.vertices; var start = startv * 3; var vertexArray = geoGroup.vertexArray; var colorArray = geoGroup.colorArray; var radiusArray = geoGroup.radiusArray; var normalArray = geoGroup.normalArray; //encode extra bits of information in the color var r = color.r; var g = color.g; var b = color.b; var negateColor = function (c) { //set sign bit var n = -c; if (n == 0) n = -0.0001; return n; }; /* for sticks, always draw caps, but we could in theory set caps in color */ //4 vertices, distinguish between p1 and p2 with neg blue var pos = start; for (var i = 0; i &lt; 4; i++) { vertexArray[pos] = from.x; normalArray[pos] = to.x; colorArray[pos] = r; pos++; vertexArray[pos] = from.y; normalArray[pos] = to.y; colorArray[pos] = g; pos++; vertexArray[pos] = from.z; normalArray[pos] = to.z; if (i &lt; 2) colorArray[pos] = b; else colorArray[pos] = negateColor(b); pos++; } geoGroup.vertices += 4; radiusArray[startv] = -radius; radiusArray[startv + 1] = radius; radiusArray[startv + 2] = -radius; radiusArray[startv + 3] = radius; //two faces var faceArray = geoGroup.faceArray; var faceoffset = geoGroup.faceidx; //not number faces, but index faceArray[faceoffset + 0] = startv; faceArray[faceoffset + 1] = startv + 1; faceArray[faceoffset + 2] = startv + 2; faceArray[faceoffset + 3] = startv + 2; faceArray[faceoffset + 4] = startv + 3; faceArray[faceoffset + 5] = startv; geoGroup.faceidx += 6; }; // draws cylinders and small spheres (at bond radius) private drawBondSticks(atom: AtomSpec, atoms: AtomSpec[], geo: Geometry) { if (!atom.style.stick) return; var style = atom.style.stick; if (style.hidden) return; var atomBondR = style.radius || this.defaultStickRadius; var doubleBondScale = style.doubleBondScaling || 0.4; var tripleBondScale = style.tripleBondScaling || 0.25; var bondDashLength = style.dashedBondConfig?.dashLength || 0.1; var bondGapLength = style.dashedBondConfig?.gapLength || 0.25; var bondR = atomBondR; var atomSingleBond = style.singleBonds || false; var atomDashedBonds = style.dashedBonds || false; var fromCap = 0, toCap = 0; var atomneedsi, atom2needsi, i, singleBond, bstyle; var cylinder1a, cylinder1b, cylinder1c, cylinder2a, cylinder2b, cylinder2c; var C1 = getColorFromStyle(atom, style); var mp, mp2, mp3; if (!atom.capDrawn &amp;&amp; atom.bonds.length &lt; 4) fromCap = 2; var selectCylDrawMethod = (bondOrder) => { var drawMethod = geo.imposter ? GLModel.drawStickImposter : GLDraw.drawCylinder; if (!atomDashedBonds &amp;&amp; bondOrder >= 1) { return drawMethod; } // draw dashes return (geo, from, to, radius, color, fromCap = 0, toCap = 0, dashLength = 0.1, gapLength = 0.25) => { var segments = this.calculateDashes(from, to, radius, dashLength, gapLength); segments.forEach(segment => { drawMethod(geo, segment.from, segment.to, radius, color, fromCap, toCap); }); }; }; for (i = 0; i &lt; atom.bonds.length; i++) { var drawCyl = selectCylDrawMethod(atom.bondOrder[i]); var j = atom.bonds[i]; // our neighbor var atom2 = atoms[j]; //parsePDB, etc should only add defined bonds mp = mp2 = mp3 = null; if (atom.index &lt; atom2.index) {// only draw if less, this // lets us combine // cylinders of the same // color var style2 = atom2.style; if (!style2.stick || style2.stick.hidden) continue; // don't sweat the details var C2 = getColorFromStyle(atom2, style2.stick); //support bond specific styles bondR = atomBondR; singleBond = atomSingleBond; if (atom.bondStyles &amp;&amp; atom.bondStyles[i]) { bstyle = atom.bondStyles[i]; if (bstyle.iswire) { continue; } if (bstyle.radius) bondR = bstyle.radius; if (bstyle.singleBond) singleBond = true; if (typeof (bstyle.color1) != "undefined") { C1 = CC.color(bstyle.color1) as Color; } if (typeof (bstyle.color2) != "undefined") { C2 = CC.color(bstyle.color2) as Color; } } var p1 = new Vector3(atom.x, atom.y, atom.z); var p2 = new Vector3(atom2.x, atom2.y, atom2.z); // draw cylinders if (atom.bondOrder[i] &lt;= 1 || singleBond || atom.bondOrder[i] > 3) { //TODO: aromatics at 4 if (atom.bondOrder[i] &lt; 1) bondR *= atom.bondOrder[i]; if (!atom2.capDrawn &amp;&amp; atom2.bonds.length &lt; 4) toCap = 2; if (C1 != C2) { mp = new Vector3().addVectors(p1, p2) .multiplyScalar(0.5); drawCyl(geo, p1, mp, bondR, C1, fromCap, 0, bondDashLength, bondGapLength); drawCyl(geo, mp, p2, bondR, C2, 0, toCap, bondDashLength, bondGapLength); } else { drawCyl(geo, p1, p2, bondR, C1, fromCap, toCap, bondDashLength, bondGapLength); } atomneedsi = atom.clickable || atom.hoverable; atom2needsi = atom2.clickable || atom2.hoverable; if (atomneedsi || atom2needsi) { if (!mp) mp = new Vector3().addVectors(p1, p2).multiplyScalar(0.5); if (atomneedsi) { var cylinder1 = new Cylinder(p1, mp, bondR); var sphere1 = new Sphere(p1, bondR); atom.intersectionShape.cylinder.push(cylinder1); atom.intersectionShape.sphere.push(sphere1); } if (atom2needsi) { var cylinder2 = new Cylinder(p2, mp, bondR); var sphere2 = new Sphere(p2, bondR); atom2.intersectionShape.cylinder.push(cylinder2); atom2.intersectionShape.sphere.push(sphere2); } } } else if (atom.bondOrder[i] > 1) { //multi bond caps var mfromCap = 0; var mtoCap = 0; if (bondR != atomBondR) { //assume jmol style multiple bonds - the radius doesn't fit within atom sphere mfromCap = 2; mtoCap = 2; } var dir = p2.clone(); var v = null; dir.sub(p1); var r, p1a, p1b, p2a, p2b; v = this.getSideBondV(atom, atom2, i); if (atom.bondOrder[i] == 2) {