Current Dev State

This commit is contained in:
Tim Lorsbach
2025-06-23 20:13:54 +02:00
parent b4f9bb277d
commit ded50edaa2
22617 changed files with 4345095 additions and 174 deletions

View File

@ -0,0 +1,647 @@
/****************************************************************************
* 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 Box2Abs = require('../../util/box2abs');
var ReObject = require('./reobject');
var scale = require('../../util/scale');
var element = require('../../chem/element');
var draw = require('../draw');
var util = require('../util');
var Vec2 = require('../../util/vec2');
var Struct = require('../../chem/struct');
function ReAtom(/* chem.Atom*/atom) {
this.init('atom');
this.a = atom; // TODO rename a to item
this.showLabel = false;
this.hydrogenOnTheLeft = false;
this.color = '#000000';
this.component = -1;
}
ReAtom.prototype = new ReObject();
ReAtom.isSelectable = function () {
return true;
};
ReAtom.prototype.getVBoxObj = function (render) {
if (this.visel.boundingBox)
return ReObject.prototype.getVBoxObj.call(this, render);
return new Box2Abs(this.a.pp, this.a.pp);
};
ReAtom.prototype.drawHighlight = function (render) {
var ret = this.makeHighlightPlate(render);
render.ctab.addReObjectPath('highlighting', this.visel, ret);
return ret;
};
ReAtom.prototype.makeHighlightPlate = function (render) {
var paper = render.paper;
var options = render.options;
var ps = scale.obj2scaled(this.a.pp, options);
return paper.circle(ps.x, ps.y, options.atomSelectionPlateRadius)
.attr(options.highlightStyle);
};
ReAtom.prototype.makeSelectionPlate = function (restruct, paper, styles) {
var ps = scale.obj2scaled(this.a.pp, restruct.render.options);
return paper.circle(ps.x, ps.y, styles.atomSelectionPlateRadius)
.attr(styles.selectionStyle);
};
ReAtom.prototype.show = function (restruct, aid, options) { // eslint-disable-line max-statements
var render = restruct.render;
var ps = scale.obj2scaled(this.a.pp, render.options);
this.hydrogenOnTheLeft = setHydrogenPos(restruct.molecule, this);
this.showLabel = labelIsVisible(restruct, render.options, this);
if (this.showLabel) {
var label = buildLabel(this, render.paper, ps, options);
var delta = 0.5 * options.lineWidth;
var rightMargin = label.rbb.width / 2;
var leftMargin = -label.rbb.width / 2;
var implh = Math.floor(this.a.implicitH);
var isHydrogen = label.text === 'H';
restruct.addReObjectPath('data', this.visel, label.path, ps, true);
var index = null;
if (options.showAtomIds) {
index = {};
index.text = aid.toString();
index.path = render.paper.text(ps.x, ps.y, index.text)
.attr({
'font': options.font,
'font-size': options.fontszsub,
'fill': '#070'
});
index.rbb = util.relBox(index.path.getBBox());
draw.recenterText(index.path, index.rbb);
restruct.addReObjectPath('indices', this.visel, index.path, ps);
}
this.setHighlight(this.highlight, render);
if (this.a.alias || this.a.pseudo) return;
var hydroIndex = null;
if (isHydrogen && implh > 0) {
hydroIndex = showHydroIndex(this, render, implh, rightMargin);
rightMargin += hydroIndex.rbb.width + delta;
restruct.addReObjectPath('data', this.visel, hydroIndex.path, ps, true);
}
if (this.a.radical != 0) {
var radical = showRadical(this, render);
restruct.addReObjectPath('data', this.visel, radical.path, ps, true);
}
if (this.a.isotope != 0) {
var isotope = showIsotope(this, render, leftMargin);
leftMargin -= isotope.rbb.width + delta;
restruct.addReObjectPath('data', this.visel, isotope.path, ps, true);
}
if (!isHydrogen && implh > 0 && displayHydrogen(options.showHydrogenLabels, this)) {
var data = showHydrogen(this, render, implh, {
hydrogen: {},
hydroIndex: hydroIndex,
rightMargin: rightMargin,
leftMargin: leftMargin
});
var hydrogen = data.hydrogen;
hydroIndex = data.hydroIndex;
rightMargin = data.rightMargin;
leftMargin = data.leftMargin;
restruct.addReObjectPath('data', this.visel, hydrogen.path, ps, true);
if (hydroIndex != null)
restruct.addReObjectPath('data', this.visel, hydroIndex.path, ps, true);
}
if (this.a.charge != 0 && options.showCharge) {
var charge = showCharge(this, render, rightMargin);
rightMargin += charge.rbb.width + delta;
restruct.addReObjectPath('data', this.visel, charge.path, ps, true);
}
if (this.a.explicitValence >= 0 && options.showValence) {
var valence = showExplicitValence(this, render, rightMargin);
rightMargin += valence.rbb.width + delta;
restruct.addReObjectPath('data', this.visel, valence.path, ps, true);
}
if (this.a.badConn && options.showValenceWarnings) {
var warning = showWarning(this, render, leftMargin, rightMargin);
restruct.addReObjectPath('warnings', this.visel, warning.path, ps, true);
}
if (index) {
/* eslint-disable no-mixed-operators*/
pathAndRBoxTranslate(index.path, index.rbb,
-0.5 * label.rbb.width - 0.5 * index.rbb.width - delta,
0.3 * label.rbb.height);
/* eslint-enable no-mixed-operators*/
}
}
if (this.a.attpnt) {
var lsb = bisectLargestSector(this, restruct.molecule);
showAttpnt(this, render, lsb, restruct.addReObjectPath.bind(restruct));
}
var aamText = getAamText(this);
var queryAttrsText = getQueryAttrsText(this);
// this includes both aam flags, if any, and query features, if any
// we render them together to avoid possible collisions
aamText = (queryAttrsText.length > 0 ? queryAttrsText + '\n' : '') + (aamText.length > 0 ? '.' + aamText + '.' : '');
if (aamText.length > 0) {
var elem = element.map[this.a.label];
var aamPath = render.paper.text(ps.x, ps.y, aamText).attr({
'font': options.font,
'font-size': options.fontszsub,
'fill': (options.atomColoring && elem && element[elem].color) ? element[elem].color : '#000'
});
var aamBox = util.relBox(aamPath.getBBox());
draw.recenterText(aamPath, aamBox);
var dir = bisectLargestSector(this, restruct.molecule);
var visel = this.visel;
var t = 3;
// estimate the shift to clear the atom label
for (var i = 0; i < visel.exts.length; ++i)
t = Math.max(t, Vec2.shiftRayBox(ps, dir, visel.exts[i].translate(ps)));
// estimate the shift backwards to account for the size of the aam/query text box itself
t += Vec2.shiftRayBox(ps, dir.negated(), Box2Abs.fromRelBox(aamBox));
dir = dir.scaled(8 + t);
pathAndRBoxTranslate(aamPath, aamBox, dir.x, dir.y);
restruct.addReObjectPath('data', this.visel, aamPath, ps, true);
}
};
function labelIsVisible(restruct, options, atom) {
var isVisibleTerminal = options.showHydrogenLabels !== 'off' &&
options.showHydrogenLabels !== 'Hetero';
if (atom.a.neighbors.length === 0 ||
(atom.a.neighbors.length < 2 && isVisibleTerminal) ||
(options.carbonExplicitly) ||
atom.a.label.toLowerCase() !== 'c' ||
(atom.a.badConn && options.showValenceWarnings) ||
atom.a.isotope != 0 ||
atom.a.radical != 0 ||
atom.a.charge != 0 ||
atom.a.explicitValence >= 0 ||
atom.a.atomList != null ||
atom.a.rglabel != null ||
atom.a.alias)
return true;
if (atom.a.neighbors.length == 2) {
var n1 = atom.a.neighbors[0];
var n2 = atom.a.neighbors[1];
var hb1 = restruct.molecule.halfBonds.get(n1);
var hb2 = restruct.molecule.halfBonds.get(n2);
var b1 = restruct.bonds.get(hb1.bid);
var b2 = restruct.bonds.get(hb2.bid);
if (b1.b.type == b2.b.type &&
b1.b.stereo == Struct.Bond.PATTERN.STEREO.NONE &&
b2.b.stereo == Struct.Bond.PATTERN.STEREO.NONE) {
if (Math.abs(Vec2.cross(hb1.dir, hb2.dir)) < 0.2)
return true;
}
}
return false;
}
function displayHydrogen(hydrogenLabels, atom) {
return ((hydrogenLabels === 'on') ||
(hydrogenLabels === 'Terminal' && atom.a.neighbors.length < 2) ||
(hydrogenLabels === 'Hetero' && atom.label.text.toLowerCase() !== 'c') ||
(hydrogenLabels === 'Terminal and Hetero' && (atom.a.neighbors.length < 2 || atom.label.text.toLowerCase() !== 'c')));
}
function setHydrogenPos(struct, atom) {
// check where should the hydrogen be put on the left of the label
if (atom.a.neighbors.length === 0) {
var elem = element.map[atom.a.label];
return !elem || !!element[elem].leftH;
}
var yl = 1,
yr = 1,
nl = 0,
nr = 0;
for (var i = 0; i < atom.a.neighbors.length; ++i) {
var d = struct.halfBonds.get(atom.a.neighbors[i]).dir;
if (d.x <= 0) {
yl = Math.min(yl, Math.abs(d.y));
nl++;
} else {
yr = Math.min(yr, Math.abs(d.y));
nr++;
}
}
return (yl < 0.51 || yr < 0.51) ? yr < yl : nr > nl;
}
function buildLabel(atom, paper, ps, options) { // eslint-disable-line max-statements
var label = {};
atom.color = 'black';
if (atom.a.atomList != null) {
label.text = atom.a.atomList.label();
} else if (atom.a.pseudo) {
label.text = atom.a.pseudo;
} else if (atom.a.alias) {
label.text = atom.a.alias;
} else if (atom.a.label === 'R#' && atom.a.rglabel != null) {
label.text = '';
for (var rgi = 0; rgi < 32; rgi++) {
if (atom.a.rglabel & (1 << rgi)) // eslint-disable-line max-depth
label.text += ('R' + (rgi + 1).toString());
}
if (label.text == '') label = 'R#'; // for structures that missed 'M RGP' tag in molfile
} else {
label.text = atom.a.label;
var elem = element.map[label.text];
if (options.atomColoring && elem)
atom.color = element[elem].color || '#000';
}
label.path = paper.text(ps.x, ps.y, label.text)
.attr({
'font': options.font,
'font-size': options.fontsz,
'fill': atom.color,
'font-style': atom.a.pseudo ? 'italic' : ''
});
label.rbb = util.relBox(label.path.getBBox());
draw.recenterText(label.path, label.rbb);
if (atom.a.atomList != null)
pathAndRBoxTranslate(label.path, label.rbb, (atom.hydrogenOnTheLeft ? -1 : 1) * (label.rbb.width - label.rbb.height) / 2, 0);
atom.label = label;
return label;
}
function showHydroIndex(atom, render, implh, rightMargin) {
var ps = scale.obj2scaled(atom.a.pp, render.options);
var options = render.options;
var delta = 0.5 * options.lineWidth;
var hydroIndex = {};
hydroIndex.text = (implh + 1).toString();
hydroIndex.path =
render.paper.text(ps.x, ps.y, hydroIndex.text)
.attr({
'font': options.font,
'font-size': options.fontszsub,
'fill': atom.color
});
hydroIndex.rbb = util.relBox(hydroIndex.path.getBBox());
draw.recenterText(hydroIndex.path, hydroIndex.rbb);
/* eslint-disable no-mixed-operators*/
pathAndRBoxTranslate(hydroIndex.path, hydroIndex.rbb,
rightMargin + 0.5 * hydroIndex.rbb.width + delta,
0.2 * atom.label.rbb.height);
/* eslint-enable no-mixed-operators*/
return hydroIndex;
}
function showRadical(atom, render) {
var ps = scale.obj2scaled(atom.a.pp, render.options);
var options = render.options;
var paper = render.paper;
var radical = {};
var hshift;
switch (atom.a.radical) {
case 1:
radical.path = paper.set();
hshift = 1.6 * options.lineWidth;
radical.path.push(
draw.radicalBullet(paper, ps.add(new Vec2(-hshift, 0)), options),
draw.radicalBullet(paper, ps.add(new Vec2(hshift, 0)), options));
radical.path.attr('fill', atom.color);
break;
case 2:
radical.path = paper.set();
radical.path.push(
draw.radicalBullet(paper, ps, options));
radical.path.attr('fill', atom.color);
break;
case 3:
radical.path = paper.set();
hshift = 1.6 * options.lineWidth;
radical.path.push(
draw.radicalCap(paper, ps.add(new Vec2(-hshift, 0)), options),
draw.radicalCap(paper, ps.add(new Vec2(hshift, 0)), options));
radical.path.attr('stroke', atom.color);
break;
default:
break;
}
radical.rbb = util.relBox(radical.path.getBBox());
var vshift = -0.5 * (atom.label.rbb.height + radical.rbb.height);
if (atom.a.radical === 3)
vshift -= options.lineWidth / 2;
pathAndRBoxTranslate(radical.path, radical.rbb,
0, vshift);
return radical;
}
function showIsotope(atom, render, leftMargin) {
var ps = scale.obj2scaled(atom.a.pp, render.options);
var options = render.options;
var delta = 0.5 * options.lineWidth;
var isotope = {};
isotope.text = atom.a.isotope.toString();
isotope.path = render.paper.text(ps.x, ps.y, isotope.text)
.attr({
'font': options.font,
'font-size': options.fontszsub,
'fill': atom.color
});
isotope.rbb = util.relBox(isotope.path.getBBox());
draw.recenterText(isotope.path, isotope.rbb);
/* eslint-disable no-mixed-operators*/
pathAndRBoxTranslate(isotope.path, isotope.rbb,
leftMargin - 0.5 * isotope.rbb.width - delta,
-0.3 * atom.label.rbb.height);
/* eslint-enable no-mixed-operators*/
return isotope;
}
function showCharge(atom, render, rightMargin) {
var ps = scale.obj2scaled(atom.a.pp, render.options);
var options = render.options;
var delta = 0.5 * options.lineWidth;
var charge = {};
charge.text = '';
var absCharge = Math.abs(atom.a.charge);
if (absCharge != 1)
charge.text = absCharge.toString();
if (atom.a.charge < 0)
charge.text += '\u2013';
else
charge.text += '+';
charge.path = render.paper.text(ps.x, ps.y, charge.text)
.attr({
'font': options.font,
'font-size': options.fontszsub,
'fill': atom.color
});
charge.rbb = util.relBox(charge.path.getBBox());
draw.recenterText(charge.path, charge.rbb);
/* eslint-disable no-mixed-operators*/
pathAndRBoxTranslate(charge.path, charge.rbb,
rightMargin + 0.5 * charge.rbb.width + delta,
-0.3 * atom.label.rbb.height);
/* eslint-enable no-mixed-operators*/
return charge;
}
function showExplicitValence(atom, render, rightMargin) {
var mapValence = {
0: '0',
1: 'I',
2: 'II',
3: 'III',
4: 'IV',
5: 'V',
6: 'VI',
7: 'VII',
8: 'VIII',
9: 'IX',
10: 'X',
11: 'XI',
12: 'XII',
13: 'XIII',
14: 'XIV'
};
var ps = scale.obj2scaled(atom.a.pp, render.options);
var options = render.options;
var delta = 0.5 * options.lineWidth;
var valence = {};
valence.text = mapValence[atom.a.explicitValence];
if (!valence.text)
throw new Error('invalid valence ' + atom.a.explicitValence.toString());
valence.text = '(' + valence.text + ')';
valence.path = render.paper.text(ps.x, ps.y, valence.text)
.attr({
'font': options.font,
'font-size': options.fontszsub,
'fill': atom.color
});
valence.rbb = util.relBox(valence.path.getBBox());
draw.recenterText(valence.path, valence.rbb);
/* eslint-disable no-mixed-operators*/
pathAndRBoxTranslate(valence.path, valence.rbb,
rightMargin + 0.5 * valence.rbb.width + delta,
-0.3 * atom.label.rbb.height);
/* eslint-enable no-mixed-operators*/
return valence;
}
function showHydrogen(atom, render, implh, data) { // eslint-disable-line max-statements
var hydroIndex = data.hydroIndex;
var hydrogenLeft = atom.hydrogenOnTheLeft;
var ps = scale.obj2scaled(atom.a.pp, render.options);
var options = render.options;
var delta = 0.5 * options.lineWidth;
var hydrogen = data.hydrogen;
hydrogen.text = 'H';
hydrogen.path = render.paper.text(ps.x, ps.y, hydrogen.text).attr({
'font': options.font,
'font-size': options.fontsz,
'fill': atom.color
});
hydrogen.rbb = util.relBox(hydrogen.path.getBBox());
draw.recenterText(hydrogen.path, hydrogen.rbb);
if (!hydrogenLeft) {
pathAndRBoxTranslate(hydrogen.path, hydrogen.rbb,
data.rightMargin + (0.5 * hydrogen.rbb.width) + delta, 0);
data.rightMargin += hydrogen.rbb.width + delta;
}
if (implh > 1) {
hydroIndex = {};
hydroIndex.text = implh.toString();
hydroIndex.path = render.paper.text(ps.x, ps.y, hydroIndex.text)
.attr({
'font': options.font,
'font-size': options.fontszsub,
'fill': atom.color
});
hydroIndex.rbb = util.relBox(hydroIndex.path.getBBox());
draw.recenterText(hydroIndex.path, hydroIndex.rbb);
if (!hydrogenLeft) {
pathAndRBoxTranslate(hydroIndex.path, hydroIndex.rbb,
data.rightMargin + (0.5 * hydroIndex.rbb.width) + delta,
0.2 * atom.label.rbb.height);
data.rightMargin += hydroIndex.rbb.width + delta;
}
}
if (hydrogenLeft) {
if (hydroIndex != null) {
pathAndRBoxTranslate(hydroIndex.path, hydroIndex.rbb,
data.leftMargin - (0.5 * hydroIndex.rbb.width) - delta,
0.2 * atom.label.rbb.height);
data.leftMargin -= hydroIndex.rbb.width + delta;
}
pathAndRBoxTranslate(hydrogen.path, hydrogen.rbb,
data.leftMargin - (0.5 * hydrogen.rbb.width) - delta, 0);
data.leftMargin -= hydrogen.rbb.width + delta;
}
return Object.assign(data, { hydrogen: hydrogen, hydroIndex: hydroIndex });
}
function showWarning(atom, render, leftMargin, rightMargin) {
var ps = scale.obj2scaled(atom.a.pp, render.options);
var delta = 0.5 * render.options.lineWidth;
var tfx = util.tfx;
var warning = {};
var y = ps.y + (atom.label.rbb.height / 2) + delta;
warning.path = render.paper.path('M{0},{1}L{2},{3}',
tfx(ps.x + leftMargin), tfx(y), tfx(ps.x + rightMargin), tfx(y))
.attr(render.options.lineattr).attr({ stroke: '#F00' });
warning.rbb = util.relBox(warning.path.getBBox());
return warning;
}
function showAttpnt(atom, render, lsb, addReObjectPath) { // eslint-disable-line max-statements
var asterisk = '';
var ps = scale.obj2scaled(atom.a.pp, render.options);
var options = render.options;
var tfx = util.tfx;
var i, c, j; // eslint-disable-line no-unused-vars
for (i = 0, c = 0; i < 4; ++i) {
var attpntText = '';
if (atom.a.attpnt & (1 << i)) {
if (attpntText.length > 0)
attpntText += ' ';
attpntText += asterisk;
for (j = 0; j < (i == 0 ? 0 : (i + 1)); ++j)
attpntText += '\'';
var pos0 = new Vec2(ps);
var pos1 = ps.addScaled(lsb, 0.7 * options.scale);
var attpntPath1 = render.paper.text(pos1.x, pos1.y, attpntText)
.attr({
'font': options.font,
'font-size': options.fontsz,
'fill': atom.color
});
var attpntRbb = util.relBox(attpntPath1.getBBox());
draw.recenterText(attpntPath1, attpntRbb);
var lsbn = lsb.negated();
/* eslint-disable no-mixed-operators*/
pos1 = pos1.addScaled(lsbn, Vec2.shiftRayBox(pos1, lsbn, Box2Abs.fromRelBox(attpntRbb)) + options.lineWidth / 2);
/* eslint-enable no-mixed-operators*/
pos0 = shiftBondEnd(atom, pos0, lsb, options.lineWidth);
var n = lsb.rotateSC(1, 0);
var arrowLeft = pos1.addScaled(n, 0.05 * options.scale).addScaled(lsbn, 0.09 * options.scale);
var arrowRight = pos1.addScaled(n, -0.05 * options.scale).addScaled(lsbn, 0.09 * options.scale);
var attpntPath = render.paper.set();
attpntPath.push(
attpntPath1,
render.paper.path('M{0},{1}L{2},{3}M{4},{5}L{2},{3}L{6},{7}', tfx(pos0.x), tfx(pos0.y), tfx(pos1.x), tfx(pos1.y), tfx(arrowLeft.x), tfx(arrowLeft.y), tfx(arrowRight.x), tfx(arrowRight.y))
.attr(render.options.lineattr).attr({ 'stroke-width': options.lineWidth / 2 })
);
addReObjectPath('indices', atom.visel, attpntPath, ps);
lsb = lsb.rotate(Math.PI / 6);
}
}
}
function getAamText(atom) {
var aamText = '';
if (atom.a.aam > 0) aamText += atom.a.aam;
if (atom.a.invRet > 0) {
if (aamText.length > 0) aamText += ',';
if (atom.a.invRet == 1) aamText += 'Inv';
else if (atom.a.invRet == 2) aamText += 'Ret';
else throw new Error('Invalid value for the invert/retain flag');
}
if (atom.a.exactChangeFlag > 0) {
if (aamText.length > 0) aamText += ',';
if (atom.a.exactChangeFlag == 1) aamText += 'ext';
else throw new Error('Invalid value for the exact change flag');
}
return aamText;
}
function getQueryAttrsText(atom) {
var queryAttrsText = '';
if (atom.a.ringBondCount != 0) {
if (atom.a.ringBondCount > 0) queryAttrsText += 'rb' + atom.a.ringBondCount.toString();
else if (atom.a.ringBondCount == -1) queryAttrsText += 'rb0';
else if (atom.a.ringBondCount == -2) queryAttrsText += 'rb*';
else throw new Error('Ring bond count invalid');
}
if (atom.a.substitutionCount != 0) {
if (queryAttrsText.length > 0) queryAttrsText += ',';
if (atom.a.substitutionCount > 0) queryAttrsText += 's' + atom.a.substitutionCount.toString();
else if (atom.a.substitutionCount == -1) queryAttrsText += 's0';
else if (atom.a.substitutionCount == -2) queryAttrsText += 's*';
else throw new Error('Substitution count invalid');
}
if (atom.a.unsaturatedAtom > 0) {
if (queryAttrsText.length > 0) queryAttrsText += ',';
if (atom.a.unsaturatedAtom == 1) queryAttrsText += 'u';
else throw new Error('Unsaturated atom invalid value');
}
if (atom.a.hCount > 0) {
if (queryAttrsText.length > 0) queryAttrsText += ',';
queryAttrsText += 'H' + (atom.a.hCount - 1).toString();
}
return queryAttrsText;
}
function pathAndRBoxTranslate(path, rbb, x, y) {
path.translateAbs(x, y);
rbb.x += x;
rbb.y += y;
}
function bisectLargestSector(atom, struct) {
var angles = [];
atom.a.neighbors.forEach(function (hbid) {
var hb = struct.halfBonds.get(hbid);
angles.push(hb.ang);
});
angles = angles.sort(function (a, b) {
return a - b;
});
var da = [];
for (var i = 0; i < angles.length - 1; ++i)
da.push(angles[(i + 1) % angles.length] - angles[i]);
da.push(angles[0] - angles[angles.length - 1] + (2 * Math.PI));
var daMax = 0;
var ang = -Math.PI / 2;
for (i = 0; i < angles.length; ++i) {
if (da[i] > daMax) {
daMax = da[i];
ang = angles[i] + (da[i] / 2);
}
}
return new Vec2(Math.cos(ang), Math.sin(ang));
}
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;
}
module.exports = ReAtom;