function rxnMerge()

in packages/ketcher-core/src/domain/serializers/mol/utils.js [138:322]


function rxnMerge(
  mols,
  nReactants,
  nProducts,
  nAgents,
  shouldReactionRelayout,
) /* Struct */ {
  // eslint-disable-line max-statements
  /* reader */
  const ret = new Struct();
  const bbReact = [];
  const bbAgent = [];
  const bbProd = [];
  const molReact = [];
  const molAgent = [];
  const molProd = [];
  let j;
  const bondLengthData = { cnt: 0, totalLength: 0 };
  for (j = 0; j < mols.length; ++j) {
    var mol = mols[j];
    const bondLengthDataMol = mol.getBondLengthData();
    bondLengthData.cnt += bondLengthDataMol.cnt;
    bondLengthData.totalLength += bondLengthDataMol.totalLength;
  }
  if (SHOULD_RESCALE_MOLECULES) {
    const avgBondLength =
      1 /
      (bondLengthData.cnt === 0
        ? 1
        : bondLengthData.totalLength / bondLengthData.cnt);
    for (j = 0; j < mols.length; ++j) {
      mol = mols[j];
      mol.scale(avgBondLength);
    }
  }

  for (j = 0; j < mols.length; ++j) {
    mol = mols[j];
    const bb = mol.getCoordBoundingBoxObj();
    if (!bb) continue; // eslint-disable-line no-continue

    var fragmentType =
      j < nReactants
        ? FRAGMENT.REACTANT // eslint-disable-line no-nested-ternary
        : j < nReactants + nProducts
        ? FRAGMENT.PRODUCT
        : FRAGMENT.AGENT;
    if (fragmentType === FRAGMENT.REACTANT) {
      bbReact.push(bb);
      molReact.push(mol);
    } else if (fragmentType === FRAGMENT.AGENT) {
      bbAgent.push(bb);
      molAgent.push(mol);
    } else if (fragmentType === FRAGMENT.PRODUCT) {
      bbProd.push(bb);
      molProd.push(mol);
    }

    mol.atoms.forEach((atom) => {
      atom.rxnFragmentType = fragmentType;
    });
  }

  function shiftMol(ret, mol, bb, xorig, over) {
    // eslint-disable-line max-params
    const d = new Vec2(
      xorig - bb.min.x,
      over ? 1 - bb.min.y : -(bb.min.y + bb.max.y) / 2,
    );
    mol.atoms.forEach((atom) => {
      atom.pp.add_(d); // eslint-disable-line no-underscore-dangle
    });

    mol.sgroups.forEach((item) => {
      if (item.pp) item.pp.add_(d); // eslint-disable-line no-underscore-dangle
    });
    bb.min.add_(d); // eslint-disable-line no-underscore-dangle
    bb.max.add_(d); // eslint-disable-line no-underscore-dangle
    mol.mergeInto(ret);
    return bb.max.x - bb.min.x;
  }

  if (shouldReactionRelayout) {
    // reaction fragment layout
    let xorig = 0;
    for (j = 0; j < molReact.length; ++j) {
      xorig += shiftMol(ret, molReact[j], bbReact[j], xorig, false) + 2.0;
    }
    xorig += 2.0;
    for (j = 0; j < molAgent.length; ++j) {
      xorig += shiftMol(ret, molAgent[j], bbAgent[j], xorig, true) + 2.0;
    }
    xorig += 2.0;

    for (j = 0; j < molProd.length; ++j) {
      xorig += shiftMol(ret, molProd[j], bbProd[j], xorig, false) + 2.0;
    }
  } else {
    for (j = 0; j < molReact.length; ++j) molReact[j].mergeInto(ret);
    for (j = 0; j < molAgent.length; ++j) molAgent[j].mergeInto(ret);
    for (j = 0; j < molProd.length; ++j) molProd[j].mergeInto(ret);
  }

  let bb1;
  let bb2;
  let x;
  let y;
  let bbReactAll = null;
  let bbProdAll = null;
  for (j = 0; j < bbReact.length - 1; ++j) {
    bb1 = bbReact[j];
    bb2 = bbReact[j + 1];

    x = (bb1.max.x + bb2.min.x) / 2;
    y = (bb1.max.y + bb1.min.y + bb2.max.y + bb2.min.y) / 4;

    ret.rxnPluses.add(new RxnPlus({ pp: new Vec2(x, y) }));
  }
  for (j = 0; j < bbReact.length; ++j) {
    if (j === 0) {
      bbReactAll = {};
      bbReactAll.max = new Vec2(bbReact[j].max);
      bbReactAll.min = new Vec2(bbReact[j].min);
    } else {
      bbReactAll.max = Vec2.max(bbReactAll.max, bbReact[j].max);
      bbReactAll.min = Vec2.min(bbReactAll.min, bbReact[j].min);
    }
  }
  for (j = 0; j < bbProd.length - 1; ++j) {
    bb1 = bbProd[j];
    bb2 = bbProd[j + 1];

    x = (bb1.max.x + bb2.min.x) / 2;
    y = (bb1.max.y + bb1.min.y + bb2.max.y + bb2.min.y) / 4;

    ret.rxnPluses.add(new RxnPlus({ pp: new Vec2(x, y) }));
  }
  for (j = 0; j < bbProd.length; ++j) {
    if (j === 0) {
      bbProdAll = {};
      bbProdAll.max = new Vec2(bbProd[j].max);
      bbProdAll.min = new Vec2(bbProd[j].min);
    } else {
      bbProdAll.max = Vec2.max(bbProdAll.max, bbProd[j].max);
      bbProdAll.min = Vec2.min(bbProdAll.min, bbProd[j].min);
    }
  }
  bb1 = bbReactAll;
  bb2 = bbProdAll;
  const defaultArrowLength = 2;

  if (!bb1 && !bb2) {
    ret.rxnArrows.add(
      new RxnArrow({
        mode: 'open-angle',
        pos: [new Vec2(0, 0), new Vec2(defaultArrowLength, 0)],
      }),
    );
  } else {
    let v1 = bb1 ? new Vec2(bb1.max.x, (bb1.max.y + bb1.min.y) / 2) : null;
    let v2 = bb2 ? new Vec2(bb2.min.x, (bb2.max.y + bb2.min.y) / 2) : null;
    const defaultOffset = 3;
    if (!v1) v1 = new Vec2(v2.x - defaultOffset, v2.y);
    if (!v2) v2 = new Vec2(v1.x + defaultOffset, v1.y);
    const arrowCenter = Vec2.lc2(v1, 0.5, v2, 0.5);
    const arrowStart = new Vec2(
      arrowCenter.x - 0.5 * defaultArrowLength,
      arrowCenter.y,
      arrowCenter.z,
    );
    const arrowEnd = new Vec2(
      arrowCenter.x + 0.5 * defaultArrowLength,
      arrowCenter.y,
      arrowCenter.z,
    );
    ret.rxnArrows.add(
      new RxnArrow({
        mode: 'open-angle',
        pos: [arrowStart, arrowEnd],
      }),
    );
  }
  ret.isReaction = true;
  return ret;
}