/**************************************************************************** * Copyright 2017 EPAM Systems * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ***************************************************************************/ var ReObject = require('./reobject'); var Struct = require('../../chem/struct'); var draw = require('../draw'); var Vec2 = require('../../util/vec2'); var util = require('../util'); var scale = require('../../util/scale'); function ReBond(/* chem.Bond*/bond) { this.init('bond'); this.b = bond; // TODO rename b to item this.doubleBondShift = 0; } ReBond.prototype = new ReObject(); ReBond.isSelectable = function () { return true; }; ReBond.prototype.drawHighlight = function (render) { var ret = this.makeHighlightPlate(render); render.ctab.addReObjectPath('highlighting', this.visel, ret); return ret; }; ReBond.prototype.makeHighlightPlate = function (render) { var options = render.options; bondRecalc(this, render.ctab, options); var c = scale.obj2scaled(this.b.center, options); return render.paper.circle(c.x, c.y, 0.8 * options.atomSelectionPlateRadius) .attr(options.highlightStyle); }; ReBond.prototype.makeSelectionPlate = function (restruct, paper, options) { bondRecalc(this, restruct, options); var c = scale.obj2scaled(this.b.center, options); return paper.circle(c.x, c.y, 0.8 * options.atomSelectionPlateRadius) .attr(options.selectionStyle); }; ReBond.prototype.show = function (restruct, bid, options) { // eslint-disable-line max-statements var render = restruct.render; var struct = restruct.molecule; var paper = render.paper; var hb1 = struct.halfBonds.get(this.b.hb1), hb2 = struct.halfBonds.get(this.b.hb2); checkStereoBold(bid, this, restruct); bondRecalc(this, restruct, options); setDoubleBondShift(this, struct); this.path = getBondPath(restruct, this, hb1, hb2); this.rbb = util.relBox(this.path.getBBox()); restruct.addReObjectPath('data', this.visel, this.path, null, true); var reactingCenter = {}; reactingCenter.path = getReactingCenterPath(render, this, hb1, hb2); if (reactingCenter.path) { reactingCenter.rbb = util.relBox(reactingCenter.path.getBBox()); restruct.addReObjectPath('data', this.visel, reactingCenter.path, null, true); } var topology = {}; topology.path = getTopologyMark(render, this, hb1, hb2); if (topology.path) { topology.rbb = util.relBox(topology.path.getBBox()); restruct.addReObjectPath('data', this.visel, topology.path, null, true); } this.setHighlight(this.highlight, render); var ipath = null; var bondIdxOff = options.subFontSize * 0.6; if (options.showBondIds) { ipath = getIdsPath(bid, paper, hb1, hb2, bondIdxOff, 0.5, 0.5, hb1.norm); restruct.addReObjectPath('indices', this.visel, ipath); } if (options.showHalfBondIds) { ipath = getIdsPath(this.b.hb1, paper, hb1, hb2, bondIdxOff, 0.8, 0.2, hb1.norm); restruct.addReObjectPath('indices', this.visel, ipath); ipath = getIdsPath(this.b.hb2, paper, hb1, hb2, bondIdxOff, 0.2, 0.8, hb2.norm); restruct.addReObjectPath('indices', this.visel, ipath); } if (options.showLoopIds && !options.showBondIds) { ipath = getIdsPath(hb1.loop, paper, hb1, hb2, bondIdxOff, 0.5, 0.5, hb2.norm); restruct.addReObjectPath('indices', this.visel, ipath); ipath = getIdsPath(hb2.loop, paper, hb1, hb2, bondIdxOff, 0.5, 0.5, hb1.norm); restruct.addReObjectPath('indices', this.visel, ipath); } }; function findIncomingStereoUpBond(atom, bid0, includeBoldStereoBond, restruct) { return atom.neighbors.findIndex(function (hbid) { var hb = restruct.molecule.halfBonds.get(hbid); var bid = hb.bid; if (bid === bid0) return false; var neibond = restruct.bonds.get(bid); if (neibond.b.type === Struct.Bond.PATTERN.TYPE.SINGLE && neibond.b.stereo === Struct.Bond.PATTERN.STEREO.UP) return neibond.b.end === hb.begin || (neibond.boldStereo && includeBoldStereoBond); return !!(neibond.b.type === Struct.Bond.PATTERN.TYPE.DOUBLE && neibond.b.stereo === Struct.Bond.PATTERN.STEREO.NONE && includeBoldStereoBond && neibond.boldStereo); }); } function findIncomingUpBonds(bid0, bond, restruct) { var halfbonds = [bond.b.begin, bond.b.end].map(function (aid) { var atom = restruct.molecule.atoms.get(aid); var pos = findIncomingStereoUpBond(atom, bid0, true, restruct); return pos < 0 ? -1 : atom.neighbors[pos]; }, this); console.assert(halfbonds.length === 2); bond.neihbid1 = restruct.atoms.get(bond.b.begin).showLabel ? -1 : halfbonds[0]; bond.neihbid2 = restruct.atoms.get(bond.b.end).showLabel ? -1 : halfbonds[1]; } function checkStereoBold(bid0, bond, restruct) { var halfbonds = [bond.b.begin, bond.b.end].map(function (aid) { var atom = restruct.molecule.atoms.get(aid); var pos = findIncomingStereoUpBond(atom, bid0, false, restruct); return pos < 0 ? -1 : atom.neighbors[pos]; }, restruct); console.assert(halfbonds.length === 2); bond.boldStereo = halfbonds[0] >= 0 && halfbonds[1] >= 0; } function getBondPath(restruct, bond, hb1, hb2) { var path = null; var render = restruct.render; var struct = restruct.molecule; var shiftA = !restruct.atoms.get(hb1.begin).showLabel; var shiftB = !restruct.atoms.get(hb2.begin).showLabel; switch (bond.b.type) { case Struct.Bond.PATTERN.TYPE.SINGLE: switch (bond.b.stereo) { case Struct.Bond.PATTERN.STEREO.UP: findIncomingUpBonds(hb1.bid, bond, restruct); if (bond.boldStereo && bond.neihbid1 >= 0 && bond.neihbid2 >= 0) path = getBondSingleStereoBoldPath(render, hb1, hb2, bond, struct); else path = getBondSingleUpPath(render, hb1, hb2, bond, struct); break; case Struct.Bond.PATTERN.STEREO.DOWN: path = getBondSingleDownPath(render, hb1, hb2); break; case Struct.Bond.PATTERN.STEREO.EITHER: path = getBondSingleEitherPath(render, hb1, hb2); break; default: path = draw.bondSingle(render.paper, hb1, hb2, render.options); break; } break; case Struct.Bond.PATTERN.TYPE.DOUBLE: findIncomingUpBonds(hb1.bid, bond, restruct); if (bond.b.stereo === Struct.Bond.PATTERN.STEREO.NONE && bond.boldStereo && bond.neihbid1 >= 0 && bond.neihbid2 >= 0) path = getBondDoubleStereoBoldPath(render, hb1, hb2, bond, struct, shiftA, shiftB); else path = getBondDoublePath(render, hb1, hb2, bond, shiftA, shiftB); break; case Struct.Bond.PATTERN.TYPE.TRIPLE: path = draw.bondTriple(render.paper, hb1, hb2, render.options); break; case Struct.Bond.PATTERN.TYPE.AROMATIC: var inAromaticLoop = (hb1.loop >= 0 && struct.loops.get(hb1.loop).aromatic) || (hb2.loop >= 0 && struct.loops.get(hb2.loop).aromatic); path = inAromaticLoop ? draw.bondSingle(render.paper, hb1, hb2, render.options) : getBondAromaticPath(render, hb1, hb2, bond, shiftA, shiftB); break; case Struct.Bond.PATTERN.TYPE.SINGLE_OR_DOUBLE: path = getSingleOrDoublePath(render, hb1, hb2); break; case Struct.Bond.PATTERN.TYPE.SINGLE_OR_AROMATIC: path = getBondAromaticPath(render, hb1, hb2, bond, shiftA, shiftB); break; case Struct.Bond.PATTERN.TYPE.DOUBLE_OR_AROMATIC: path = getBondAromaticPath(render, hb1, hb2, bond, shiftA, shiftB); break; case Struct.Bond.PATTERN.TYPE.ANY: path = draw.bondAny(render.paper, hb1, hb2, render.options); break; default: throw new Error('Bond type ' + bond.b.type + ' not supported'); } return path; } /* Get Path */ function getBondSingleUpPath(render, hb1, hb2, bond, struct) { // eslint-disable-line max-params var a = hb1.p, b = hb2.p, n = hb1.norm; var options = render.options; var bsp = 0.7 * options.stereoBond; var b2 = b.addScaled(n, bsp); var b3 = b.addScaled(n, -bsp); if (bond.neihbid2 >= 0) { // if the end is shared with another up-bond heading this way var coords = stereoUpBondGetCoordinates(hb2, bond.neihbid2, options.stereoBond, struct); b2 = coords[0]; b3 = coords[1]; } return draw.bondSingleUp(render.paper, a, b2, b3, options); } function getBondSingleStereoBoldPath(render, hb1, hb2, bond, struct) { // eslint-disable-line max-params var options = render.options; var coords1 = stereoUpBondGetCoordinates(hb1, bond.neihbid1, options.stereoBond, struct); var coords2 = stereoUpBondGetCoordinates(hb2, bond.neihbid2, options.stereoBond, struct); var a1 = coords1[0]; var a2 = coords1[1]; var a3 = coords2[0]; var a4 = coords2[1]; return draw.bondSingleStereoBold(render.paper, a1, a2, a3, a4, options); } function getBondDoubleStereoBoldPath(render, hb1, hb2, bond, struct, shiftA, shiftB) { // eslint-disable-line max-params var a = hb1.p, b = hb2.p, n = hb1.norm, shift = bond.doubleBondShift; var bsp = 1.5 * render.options.stereoBond; var b1 = a.addScaled(n, bsp * shift); var b2 = b.addScaled(n, bsp * shift); if (shift > 0) { if (shiftA) b1 = b1.addScaled(hb1.dir, bsp * getBondLineShift(hb1.rightCos, hb1.rightSin)); if (shiftB) b2 = b2.addScaled(hb1.dir, -bsp * getBondLineShift(hb2.leftCos, hb2.leftSin)); } else if (shift < 0) { if (shiftA) b1 = b1.addScaled(hb1.dir, bsp * getBondLineShift(hb1.leftCos, hb1.leftSin)); if (shiftB) b2 = b2.addScaled(hb1.dir, -bsp * getBondLineShift(hb2.rightCos, hb2.rightSin)); } var sgBondPath = getBondSingleStereoBoldPath(render, hb1, hb2, bond, struct); return draw.bondDoubleStereoBold(render.paper, sgBondPath, b1, b2, render.options); } function getBondLineShift(cos, sin) { if (sin < 0 || Math.abs(cos) > 0.9) return 0; return sin / (1 - cos); } function stereoUpBondGetCoordinates(hb, neihbid, bondSpace, struct) { var neihb = struct.halfBonds.get(neihbid); var cos = Vec2.dot(hb.dir, neihb.dir); var sin = Vec2.cross(hb.dir, neihb.dir); var cosHalf = Math.sqrt(0.5 * (1 - cos)); var biss = neihb.dir.rotateSC((sin >= 0 ? -1 : 1) * cosHalf, Math.sqrt(0.5 * (1 + cos))); var denomAdd = 0.3; var scale = 0.7; var a1 = hb.p.addScaled(biss, scale * bondSpace / (cosHalf + denomAdd)); var a2 = hb.p.addScaled(biss.negated(), scale * bondSpace / (cosHalf + denomAdd)); return sin > 0 ? [a1, a2] : [a2, a1]; } function getBondSingleDownPath(render, hb1, hb2) { var a = hb1.p, b = hb2.p; var options = render.options; var d = b.sub(a); var len = d.length() + 0.2; d = d.normalized(); var interval = 1.2 * options.lineWidth; var nlines = Math.max(Math.floor((len - options.lineWidth) / (options.lineWidth + interval)), 0) + 2; var step = len / (nlines - 1); return draw.bondSingleDown(render.paper, hb1, d, nlines, step, options); } function getBondSingleEitherPath(render, hb1, hb2) { var a = hb1.p, b = hb2.p; var options = render.options; var d = b.sub(a); var len = d.length(); d = d.normalized(); var interval = 0.6 * options.lineWidth; var nlines = Math.max(Math.floor((len - options.lineWidth) / (options.lineWidth + interval)), 0) + 2; var step = len / (nlines - 0.5); return draw.bondSingleEither(render.paper, hb1, d, nlines, step, options); } function getBondDoublePath(render, hb1, hb2, bond, shiftA, shiftB) { // eslint-disable-line max-params, max-statements var cisTrans = bond.b.stereo === Struct.Bond.PATTERN.STEREO.CIS_TRANS; var a = hb1.p, b = hb2.p, n = hb1.norm, shift = cisTrans ? 0 : bond.doubleBondShift; var options = render.options; var bsp = options.bondSpace / 2; var s1 = bsp + (shift * bsp), s2 = -bsp + (shift * bsp); var a1 = a.addScaled(n, s1); var b1 = b.addScaled(n, s1); var a2 = a.addScaled(n, s2); var b2 = b.addScaled(n, s2); if (shift > 0) { if (shiftA) { a1 = a1.addScaled(hb1.dir, options.bondSpace * getBondLineShift(hb1.rightCos, hb1.rightSin)); } if (shiftB) { b1 = b1.addScaled(hb1.dir, -options.bondSpace * getBondLineShift(hb2.leftCos, hb2.leftSin)); } } else if (shift < 0) { if (shiftA) { a2 = a2.addScaled(hb1.dir, options.bondSpace * getBondLineShift(hb1.leftCos, hb1.leftSin)); } if (shiftB) { b2 = b2.addScaled(hb1.dir, -options.bondSpace * getBondLineShift(hb2.rightCos, hb2.rightSin)); } } return draw.bondDouble(render.paper, a1, a2, b1, b2, cisTrans, options); } function getSingleOrDoublePath(render, hb1, hb2) { var a = hb1.p, b = hb2.p; var options = render.options; var nSect = (Vec2.dist(a, b) / (options.bondSpace + options.lineWidth)).toFixed() - 0; if (!(nSect & 1)) nSect += 1; return draw.bondSingleOrDouble(render.paper, hb1, hb2, nSect, options); } function getBondAromaticPath(render, hb1, hb2, bond, shiftA, shiftB) { // eslint-disable-line max-params var dashdotPattern = [0.125, 0.125, 0.005, 0.125]; var mark = null, dash = null; var options = render.options; var bondShift = bond.doubleBondShift; if (bond.b.type == Struct.Bond.PATTERN.TYPE.SINGLE_OR_AROMATIC) { mark = bondShift > 0 ? 1 : 2; dash = dashdotPattern.map(function (v) { return v * options.scale; }); } if (bond.b.type == Struct.Bond.PATTERN.TYPE.DOUBLE_OR_AROMATIC) { mark = 3; dash = dashdotPattern.map(function (v) { return v * options.scale; }); } var paths = getAromaticBondPaths(hb1, hb2, bondShift, shiftA, shiftB, options.bondSpace, mark, dash); return draw.bondAromatic(render.paper, paths, bondShift, options); } function getAromaticBondPaths(hb1, hb2, shift, shiftA, shiftB, bondSpace, mask, dash) { // eslint-disable-line max-params, max-statements var a = hb1.p, b = hb2.p, n = hb1.norm; var bsp = bondSpace / 2; var s1 = bsp + (shift * bsp), s2 = -bsp + (shift * bsp); var a2 = a.addScaled(n, s1); var b2 = b.addScaled(n, s1); var a3 = a.addScaled(n, s2); var b3 = b.addScaled(n, s2); if (shift > 0) { if (shiftA) { a2 = a2.addScaled(hb1.dir, bondSpace * getBondLineShift(hb1.rightCos, hb1.rightSin)); } if (shiftB) { b2 = b2.addScaled(hb1.dir, -bondSpace * getBondLineShift(hb2.leftCos, hb2.leftSin)); } } else if (shift < 0) { if (shiftA) { a3 = a3.addScaled(hb1.dir, bondSpace * getBondLineShift(hb1.leftCos, hb1.leftSin)); } if (shiftB) { b3 = b3.addScaled(hb1.dir, -bondSpace * getBondLineShift(hb2.rightCos, hb2.rightSin)); } } return draw.aromaticBondPaths(a2, a3, b2, b3, mask, dash); } function getReactingCenterPath(render, bond, hb1, hb2) { // eslint-disable-line max-statements var a = hb1.p, b = hb2.p; var c = b.add(a).scaled(0.5); var d = b.sub(a).normalized(); var n = d.rotateSC(1, 0); var p = []; var lw = render.options.lineWidth, bs = render.options.bondSpace / 2; var alongIntRc = lw, // half interval along for CENTER alongIntMadeBroken = 2 * lw, // half interval between along for MADE_OR_BROKEN alongSz = 1.5 * bs, // half size along for CENTER acrossInt = 1.5 * bs, // half interval across for CENTER acrossSz = 3.0 * bs, // half size across for all tiltTan = 0.2; // tangent of the tilt angle switch (bond.b.reactingCenterStatus) { case Struct.Bond.PATTERN.REACTING_CENTER.NOT_CENTER: // X p.push(c.addScaled(n, acrossSz).addScaled(d, tiltTan * acrossSz)); p.push(c.addScaled(n, -acrossSz).addScaled(d, -tiltTan * acrossSz)); p.push(c.addScaled(n, acrossSz).addScaled(d, -tiltTan * acrossSz)); p.push(c.addScaled(n, -acrossSz).addScaled(d, tiltTan * acrossSz)); break; case Struct.Bond.PATTERN.REACTING_CENTER.CENTER: // # p.push(c.addScaled(n, acrossSz).addScaled(d, tiltTan * acrossSz).addScaled(d, alongIntRc)); p.push(c.addScaled(n, -acrossSz).addScaled(d, -tiltTan * acrossSz).addScaled(d, alongIntRc)); p.push(c.addScaled(n, acrossSz).addScaled(d, tiltTan * acrossSz).addScaled(d, -alongIntRc)); p.push(c.addScaled(n, -acrossSz).addScaled(d, -tiltTan * acrossSz).addScaled(d, -alongIntRc)); p.push(c.addScaled(d, alongSz).addScaled(n, acrossInt)); p.push(c.addScaled(d, -alongSz).addScaled(n, acrossInt)); p.push(c.addScaled(d, alongSz).addScaled(n, -acrossInt)); p.push(c.addScaled(d, -alongSz).addScaled(n, -acrossInt)); break; // case Bond.PATTERN.REACTING_CENTER.UNCHANGED: // o // //draw a circle // break; case Struct.Bond.PATTERN.REACTING_CENTER.MADE_OR_BROKEN: p.push(c.addScaled(n, acrossSz).addScaled(d, alongIntMadeBroken)); p.push(c.addScaled(n, -acrossSz).addScaled(d, alongIntMadeBroken)); p.push(c.addScaled(n, acrossSz).addScaled(d, -alongIntMadeBroken)); p.push(c.addScaled(n, -acrossSz).addScaled(d, -alongIntMadeBroken)); break; case Struct.Bond.PATTERN.REACTING_CENTER.ORDER_CHANGED: p.push(c.addScaled(n, acrossSz)); p.push(c.addScaled(n, -acrossSz)); break; case Struct.Bond.PATTERN.REACTING_CENTER.MADE_OR_BROKEN_AND_CHANGED: p.push(c.addScaled(n, acrossSz).addScaled(d, alongIntMadeBroken)); p.push(c.addScaled(n, -acrossSz).addScaled(d, alongIntMadeBroken)); p.push(c.addScaled(n, acrossSz).addScaled(d, -alongIntMadeBroken)); p.push(c.addScaled(n, -acrossSz).addScaled(d, -alongIntMadeBroken)); p.push(c.addScaled(n, acrossSz)); p.push(c.addScaled(n, -acrossSz)); break; default: return null; } return draw.reactingCenter(render.paper, p, render.options); } function getTopologyMark(render, bond, hb1, hb2) { // eslint-disable-line max-statements var options = render.options; var mark = null; if (bond.b.topology == Struct.Bond.PATTERN.TOPOLOGY.RING) mark = 'rng'; else if (bond.b.topology == Struct.Bond.PATTERN.TOPOLOGY.CHAIN) mark = 'chn'; else return null; var a = hb1.p, b = hb2.p; var c = b.add(a).scaled(0.5); var d = b.sub(a).normalized(); var n = d.rotateSC(1, 0); var fixed = options.lineWidth; if (bond.doubleBondShift > 0) n = n.scaled(-bond.doubleBondShift); else if (bond.doubleBondShift == 0) fixed += options.bondSpace / 2; var s = new Vec2(2, 1).scaled(options.bondSpace); if (bond.b.type == Struct.Bond.PATTERN.TYPE.TRIPLE) fixed += options.bondSpace; var p = c.add(new Vec2(n.x * (s.x + fixed), n.y * (s.y + fixed))); return draw.topologyMark(render.paper, p, mark, options); } function getIdsPath(bid, paper, hb1, hb2, bondIdxOff, param1, param2, norm) { // eslint-disable-line max-params var pb = Vec2.lc(hb1.p, param1, hb2.p, param2, norm, bondIdxOff); var ipath = paper.text(pb.x, pb.y, bid.toString()); var irbb = util.relBox(ipath.getBBox()); draw.recenterText(ipath, irbb); return ipath; } /* ----- */ function setDoubleBondShift(bond, struct) { var loop1, loop2; loop1 = struct.halfBonds.get(bond.b.hb1).loop; loop2 = struct.halfBonds.get(bond.b.hb2).loop; if (loop1 >= 0 && loop2 >= 0) { var d1 = struct.loops.get(loop1).dblBonds; var d2 = struct.loops.get(loop2).dblBonds; var n1 = struct.loops.get(loop1).hbs.length; var n2 = struct.loops.get(loop2).hbs.length; bond.doubleBondShift = selectDoubleBondShift(n1, n2, d1, d2); } else if (loop1 >= 0) { bond.doubleBondShift = -1; } else if (loop2 >= 0) { bond.doubleBondShift = 1; } else { bond.doubleBondShift = selectDoubleBondShiftChain(struct, bond); } } function bondRecalc(bond, restruct, options) { var render = restruct.render; var atom1 = restruct.atoms.get(bond.b.begin); var atom2 = restruct.atoms.get(bond.b.end); var p1 = scale.obj2scaled(atom1.a.pp, render.options); var p2 = scale.obj2scaled(atom2.a.pp, render.options); var hb1 = restruct.molecule.halfBonds.get(bond.b.hb1); var hb2 = restruct.molecule.halfBonds.get(bond.b.hb2); hb1.p = shiftBondEnd(atom1, p1, hb1.dir, 2 * options.lineWidth); hb2.p = shiftBondEnd(atom2, p2, hb2.dir, 2 * options.lineWidth); bond.b.center = Vec2.lc2(atom1.a.pp, 0.5, atom2.a.pp, 0.5); bond.b.len = Vec2.dist(p1, p2); bond.b.sb = options.lineWidth * 5; /* eslint-disable no-mixed-operators*/ bond.b.sa = Math.max(bond.b.sb, bond.b.len / 2 - options.lineWidth * 2); /* eslint-enable no-mixed-operators*/ bond.b.angle = Math.atan2(hb1.dir.y, hb1.dir.x) * 180 / Math.PI; } function shiftBondEnd(atom, pos0, dir, margin) { var t = 0; var visel = atom.visel; for (var k = 0; k < visel.exts.length; ++k) { var box = visel.exts[k].translate(pos0); t = Math.max(t, Vec2.shiftRayBox(pos0, dir, box)); } if (t > 0) pos0 = pos0.addScaled(dir, t + margin); return pos0; } function selectDoubleBondShift(n1, n2, d1, d2) { if (n1 == 6 && n2 != 6 && (d1 > 1 || d2 == 1)) return -1; if (n2 == 6 && n1 != 6 && (d2 > 1 || d1 == 1)) return 1; if (n2 * d1 > n1 * d2) return -1; if (n2 * d1 < n1 * d2) return 1; if (n2 > n1) return -1; return 1; } function selectDoubleBondShiftChain(struct, bond) { var hb1 = struct.halfBonds.get(bond.b.hb1); var hb2 = struct.halfBonds.get(bond.b.hb2); var nLeft = (hb1.leftSin > 0.3 ? 1 : 0) + (hb2.rightSin > 0.3 ? 1 : 0); var nRight = (hb2.leftSin > 0.3 ? 1 : 0) + (hb1.rightSin > 0.3 ? 1 : 0); if (nLeft > nRight) return -1; if (nLeft < nRight) return 1; if ((hb1.leftSin > 0.3 ? 1 : 0) + (hb1.rightSin > 0.3 ? 1 : 0) == 1) return 1; return 0; } module.exports = ReBond;