/// <summary> /// Distribute the bonded atoms (neighbours) of an atom such that they fill the /// remaining space around an atom in a geometrically nice way. /// IMPORTANT: This method is not supposed to handle the /// case of one or no place neighbor. In the case of /// one placed neighbor, the chain placement methods /// should be used. /// </summary> /// <param name="atom">The atom whose partners are to be placed</param> /// <param name="placedNeighbours">The atoms which are already placed</param> /// <param name="unplacedNeighbours">The partners to be placed</param> /// <param name="bondLength">The standard bond length for the newly placed atoms</param> /// <param name="sharedAtomsCenter">The 2D centre of the placed atoms</param> public void DistributePartners(IAtom atom, IAtomContainer placedNeighbours, Vector2 sharedAtomsCenter, IAtomContainer unplacedNeighbours, double bondLength) { double occupiedAngle = 0; IAtom[] sortedAtoms = null; double startAngle = 0.0; double addAngle = 0.0; double radius = 0.0; double remainingAngle = 0.0; // calculate the direction away from the already placed partners of atom var sharedAtomsCenterVector = sharedAtomsCenter; var newDirection = atom.Point2D.Value; var occupiedDirection = sharedAtomsCenter; occupiedDirection = Vector2.Subtract(occupiedDirection, newDirection); // if the placing on the centre atom we get NaNs just give a arbitrary direction the // rest works it's self out if (Math.Abs(occupiedDirection.Length()) < 0.001) { occupiedDirection = new Vector2(0, 1); } Debug.WriteLine("distributePartners->occupiedDirection.Lenght(): " + occupiedDirection.Length()); var atomsToDraw = new List <IAtom>(); Debug.WriteLine($"Number of shared atoms: {placedNeighbours.Atoms.Count}"); // IMPORTANT: This method is not supposed to handle the case of one or // no place neighbor. In the case of one placed neighbor, the chain // placement methods should be used. if (placedNeighbours.Atoms.Count == 1) { Debug.WriteLine("Only one neighbour..."); for (int f = 0; f < unplacedNeighbours.Atoms.Count; f++) { atomsToDraw.Add(unplacedNeighbours.Atoms[f]); } addAngle = Math.PI * 2 / (unplacedNeighbours.Atoms.Count + placedNeighbours.Atoms.Count); // IMPORTANT: At this point we need a calculation of the start // angle. Not done yet. IAtom placedAtom = placedNeighbours.Atoms[0]; double xDiff = placedAtom.Point2D.Value.X - atom.Point2D.Value.X; double yDiff = placedAtom.Point2D.Value.Y - atom.Point2D.Value.Y; Debug.WriteLine("distributePartners->xdiff: " + Vectors.RadianToDegree(xDiff)); Debug.WriteLine("distributePartners->ydiff: " + Vectors.RadianToDegree(yDiff)); startAngle = GeometryUtil.GetAngle(xDiff, yDiff); Debug.WriteLine("distributePartners->angle: " + Vectors.RadianToDegree(startAngle)); PopulatePolygonCorners(atomsToDraw, atom.Point2D.Value, startAngle, addAngle, bondLength); return; } else if (placedNeighbours.Atoms.Count == 0) { Debug.WriteLine("First atom..."); for (int f = 0; f < unplacedNeighbours.Atoms.Count; f++) { atomsToDraw.Add(unplacedNeighbours.Atoms[f]); } addAngle = Math.PI * 2.0 / unplacedNeighbours.Atoms.Count; // IMPORTANT: At this point we need a calculation of the start // angle. Not done yet. startAngle = 0.0; PopulatePolygonCorners(atomsToDraw, atom.Point2D.Value, startAngle, addAngle, bondLength); return; } if (DoAngleSnap(atom, placedNeighbours)) { int numTerminal = 0; foreach (var unplaced in unplacedNeighbours.Atoms) { if (Molecule.GetConnectedBonds(unplaced).Count() == 1) { numTerminal++; } } if (numTerminal == unplacedNeighbours.Atoms.Count) { var a = NewVector(placedNeighbours.Atoms[0].Point2D.Value, atom.Point2D.Value); var b = NewVector(placedNeighbours.Atoms[1].Point2D.Value, atom.Point2D.Value); var d1 = GeometryUtil.GetAngle(a.X, a.Y); var d2 = GeometryUtil.GetAngle(b.X, b.Y); var sweep = Vectors.Angle(a, b); if (sweep < Math.PI) { sweep = 2 * Math.PI - sweep; } startAngle = d2; if (d1 > d2 && d1 - d2 < Math.PI || d2 - d1 >= Math.PI) { startAngle = d1; } sweep /= (1 + unplacedNeighbours.Atoms.Count); PopulatePolygonCorners(unplacedNeighbours.Atoms, atom.Point2D.Value, startAngle, sweep, bondLength); MarkPlaced(unplacedNeighbours); return; } else { atom.RemoveProperty(MacroCycleLayout.MACROCYCLE_ATOM_HINT); } } // if the least hindered side of the atom is clearly defined (bondLength / 10 is an arbitrary value that seemed reasonable) //newDirection.Sub(sharedAtomsCenterVector); sharedAtomsCenterVector -= newDirection; newDirection = sharedAtomsCenterVector; newDirection = Vector2.Normalize(newDirection); newDirection = newDirection * bondLength; newDirection = -newDirection; Debug.WriteLine($"distributePartners->newDirection.Lenght(): {newDirection.Length()}"); var distanceMeasure = atom.Point2D.Value; distanceMeasure += newDirection; // get the two sharedAtom partners with the smallest distance to the new center sortedAtoms = placedNeighbours.Atoms.ToArray(); GeometryUtil.SortBy2DDistance(sortedAtoms, distanceMeasure); var closestPoint1 = sortedAtoms[0].Point2D.Value; var closestPoint2 = sortedAtoms[1].Point2D.Value; closestPoint1 -= atom.Point2D.Value; closestPoint2 -= atom.Point2D.Value; occupiedAngle = Vectors.Angle(closestPoint1, occupiedDirection); occupiedAngle += Vectors.Angle(closestPoint2, occupiedDirection); var angle1 = GeometryUtil.GetAngle( sortedAtoms[0].Point2D.Value.X - atom.Point2D.Value.X, sortedAtoms[0].Point2D.Value.Y - atom.Point2D.Value.Y); var angle2 = GeometryUtil.GetAngle( sortedAtoms[1].Point2D.Value.X - atom.Point2D.Value.X, sortedAtoms[1].Point2D.Value.Y - atom.Point2D.Value.Y); var angle3 = GeometryUtil.GetAngle( distanceMeasure.X - atom.Point2D.Value.X, distanceMeasure.Y - atom.Point2D.Value.Y); if (debug) { try { Debug.WriteLine($"distributePartners->sortedAtoms[0]: {(Molecule.Atoms.IndexOf(sortedAtoms[0]) + 1)}"); Debug.WriteLine($"distributePartners->sortedAtoms[1]: {(Molecule.Atoms.IndexOf(sortedAtoms[1]) + 1)}"); Debug.WriteLine($"distributePartners->angle1: {Vectors.RadianToDegree(angle1)}"); Debug.WriteLine($"distributePartners->angle2: {Vectors.RadianToDegree(angle2)}"); } catch (Exception exc) { Debug.WriteLine(exc); } } IAtom startAtom = null; if (angle1 > angle3) { if (angle1 - angle3 < Math.PI) { startAtom = sortedAtoms[1]; } else { // 12 o'clock is between the two vectors startAtom = sortedAtoms[0]; } } else { if (angle3 - angle1 < Math.PI) { startAtom = sortedAtoms[0]; } else { // 12 o'clock is between the two vectors startAtom = sortedAtoms[1]; } } remainingAngle = (2 * Math.PI) - occupiedAngle; addAngle = remainingAngle / (unplacedNeighbours.Atoms.Count + 1); if (debug) { try { Debug.WriteLine($"distributePartners->startAtom: {(Molecule.Atoms.IndexOf(startAtom) + 1)}"); Debug.WriteLine($"distributePartners->remainingAngle: {Vectors.RadianToDegree(remainingAngle)}"); Debug.WriteLine($"distributePartners->addAngle: {Vectors.RadianToDegree(addAngle)}"); Debug.WriteLine($"distributePartners-> partners.Atoms.Count: {unplacedNeighbours.Atoms.Count}"); } catch (Exception exc) { Debug.WriteLine(exc); } } for (int f = 0; f < unplacedNeighbours.Atoms.Count; f++) { atomsToDraw.Add(unplacedNeighbours.Atoms[f]); } radius = bondLength; startAngle = GeometryUtil.GetAngle(startAtom.Point2D.Value.X - atom.Point2D.Value.X, startAtom.Point2D.Value.Y - atom.Point2D.Value.Y); Debug.WriteLine($"Before check: distributePartners->startAngle: {startAngle}"); Debug.WriteLine($"After check: distributePartners->startAngle: {startAngle}"); PopulatePolygonCorners(atomsToDraw, atom.Point2D.Value, startAngle, addAngle, radius); }