Example #1
0
        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);
        }
Example #2
0
        /// <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));
            }
        }
Example #3
0
        /// <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));
        }