public void LargestGapSouthWest() { Vector2 vector = VecmathUtil.NewVectorInLargestGap(new[] { new Vector2(0, 1), new Vector2(1, 0) }); Assert.AreEqual(-0.707, vector.X, 0.01); Assert.AreEqual(-0.707, vector.Y, 0.01); Assert.AreEqual(1, vector.Length(), 0.01); }
public void LargestGapEast() { Vector2 vector = VecmathUtil.NewVectorInLargestGap(new[] { new Vector2(1, 1), new Vector2(1, -1), new Vector2(-1, -1), new Vector2(-1, 1), new Vector2(-1, 0), new Vector2(0, 1), new Vector2(0, -1) }); Assert.AreEqual(1, vector.X, 0.01); Assert.AreEqual(0, vector.Y, 0.01); Assert.AreEqual(1, vector.Length(), 0.01); }
/// <summary> /// Generate a new annotation vector for an atom using the connected bonds and any other occupied /// space (auxiliary vectors). The fall back method is to use the largest available space but /// some common cases are handled differently. For example, when the number of bonds is two /// the annotation is placed in the acute angle of the bonds (providing there is space). This /// improves labelling of atoms saturated rings. When there are three bonds and two are 'plain' /// the label is again placed in the acute section of the plain bonds. /// </summary> /// <param name="atom">the atom having an annotation</param> /// <param name="bonds">the bonds connected to the atom</param> /// <param name="auxVectors">additional vectors to avoid (filled spaced)</param> /// <returns>unit vector along which the annotation should be placed.</returns> /// <seealso cref="IsPlainBond(IBond)"/> /// <seealso cref="VecmathUtil.NewVectorInLargestGap(IList{Vector2})"/> internal static Vector2 NewAtomAnnotationVector(IAtom atom, IEnumerable <IBond> bonds, List <Vector2> auxVectors) { var vectors = new List <Vector2>(); foreach (var bond in bonds) { vectors.Add(VecmathUtil.NewUnitVector(atom, bond)); } if (vectors.Count == 0) { // no bonds, place below if (auxVectors.Count == 0) { return(new Vector2(0, -1)); } if (auxVectors.Count == 1) { return(VecmathUtil.Negate(auxVectors[0])); } return(VecmathUtil.NewVectorInLargestGap(auxVectors)); } else if (vectors.Count == 1) { // 1 bond connected // H0, then label simply appears on the opposite side if (auxVectors.Count == 0) { return(VecmathUtil.Negate(vectors[0])); } // !H0, then place it in the largest gap vectors.AddRange(auxVectors); return(VecmathUtil.NewVectorInLargestGap(vectors)); } else if (vectors.Count == 2 && auxVectors.Count == 0) { // 2 bonds connected to an atom with no hydrogen labels // sum the vectors such that the label appears in the acute/nook of the two bonds var combined = VecmathUtil.Sum(vectors[0], vectors[1]); // shallow angle (< 30 deg) means the label probably won't fit if (Vectors.Angle(vectors[0], vectors[1]) < Vectors.DegreeToRadian(65)) { combined = Vector2.Negate(combined); } else { // flip vector if either bond is a non-single bond or a wedge, this will // place the label in the largest space. // However - when both bonds are wedged (consider a bridging system) to // keep the label in the nook of the wedges var bonds_ = bonds.ToList(); if ((!IsPlainBond(bonds_[0]) || !IsPlainBond(bonds_[1])) && !(IsWedged(bonds_[0]) && IsWedged(bonds_[1]))) { combined = Vector2.Negate(combined); } } combined = Vector2.Normalize(combined); // did we divide by 0? whoops - this happens when the bonds are collinear if (double.IsNaN(combined.Length())) { return(VecmathUtil.NewVectorInLargestGap(vectors)); } return(combined); } else { if (vectors.Count == 3 && auxVectors.Count == 0) { // 3 bonds connected to an atom with no hydrogen label // the easy and common case is to check when two bonds are plain // (i.e. non-stereo sigma bonds) and use those. This gives good // placement for fused conjugated rings var plainVectors = new List <Vector2>(); var wedgeVectors = new List <Vector2>(); foreach (var bond in bonds) { if (IsPlainBond(bond)) { plainVectors.Add(VecmathUtil.NewUnitVector(atom, bond)); } if (IsWedged(bond)) { wedgeVectors.Add(VecmathUtil.NewUnitVector(atom, bond)); } } if (plainVectors.Count == 2) { return(VecmathUtil.Sum(plainVectors[0], plainVectors[1])); } else if (plainVectors.Count + wedgeVectors.Count == 2) { plainVectors.AddRange(wedgeVectors); return(VecmathUtil.Sum(plainVectors[0], plainVectors[1])); } } // the default option is to find the largest gap if (auxVectors.Count > 0) { vectors.AddRange(auxVectors); } return(VecmathUtil.NewVectorInLargestGap(vectors)); } }