public void TestSum() { Vector2 vector = VecmathUtil.Sum(new Vector2(4, 2), new Vector2(2, 5)); Assert.AreEqual(6, vector.X, 0.01); Assert.AreEqual(7, vector.Y, 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)); } }
/// <summary> /// Generate an annotation 'label' for an atom (located at 'basePoint'). The label is offset from /// the basePoint by the provided 'distance' and 'direction'. /// </summary> /// <param name="basePoint">the relative (0,0) reference</param> /// <param name="label">the annotation text</param> /// <param name="direction">the direction along which the label is laid out</param> /// <param name="distance">the distance along the direct to travel</param> /// <param name="scale">the font scale of the label</param> /// <param name="font">the font to use</param> /// <param name="symbol">the atom symbol to avoid overlap with</param> /// <returns>the position text outline for the annotation</returns> internal static TextOutline GenerateAnnotation(Vector2 basePoint, string label, Vector2 direction, double distance, double scale, Typeface font, double emSize, AtomSymbol symbol) { var italicHint = label.StartsWith(ItalicDisplayPrefix); label = italicHint ? label.Substring(ItalicDisplayPrefix.Length) : label; var annFont = italicHint ? new Typeface(font.FontFamily, WPF.FontStyles.Italic, font.Weight, font.Stretch) : font; var annOutline = new TextOutline(label, annFont, emSize).Resize(scale, -scale); // align to the first or last character of the annotation depending on the direction var center = direction.X > 0.3 ? annOutline.GetFirstGlyphCenter() : direction.X < -0.3 ? annOutline.GetLastGlyphCenter() : annOutline.GetCenter(); // Avoid atom symbol if (symbol != null) { var intersect = symbol.GetConvexHull().Intersect(VecmathUtil.ToPoint(basePoint), VecmathUtil.ToPoint(VecmathUtil.Sum(basePoint, direction))); // intersect should never be null be check against this if (intersect != null) { basePoint = VecmathUtil.ToVector(intersect); } } direction *= distance; direction += basePoint; // move to position return(annOutline.Translate(direction.X - center.X, direction.Y - center.Y)); }