예제 #1
0
        /// <summary>
        /// Generate a 2D directional vector that is located in the middle of the largest angular extent
        /// (i.e. has the most space). For example if we have a two vectors, one pointing up and one
        /// pointing right we have to extents (.5π and 1.5π). The new vector would be pointing down and
        /// to the left in the middle of the 1.5π extent.
        /// </summary>
        /// <param name="vectors">list of vectors</param>
        /// <returns>the new vector</returns>
        public static Vector2 NewVectorInLargestGap(IList <Vector2> vectors)
        {
            Debug.Assert(vectors.Count > 1);
            double[] extents = VecmathUtil.Extents(vectors);
            Array.Sort(extents);

            // find and store the index of the largest extent
            double max   = -1;
            int    index = -1;

            for (int i = 0; i < vectors.Count; i++)
            {
                double extent = extents[(i + 1) % vectors.Count] - extents[i];
                if (extent < 0)
                {
                    extent += TAU;
                }
                if (extent > max)
                {
                    max   = extent;
                    index = i;
                }
            }

            Debug.Assert(index >= 0);

            double mid   = (max / 2);
            double theta = extents[index] + mid;

            return(new Vector2(Math.Cos(theta), Math.Sin(theta)));
        }
예제 #2
0
        public void TestNegate()
        {
            Vector2 vector = VecmathUtil.Negate(new Vector2(4, 2));

            Assert.AreEqual(-4, vector.X, 0.01);
            Assert.AreEqual(-2, vector.Y, 0.01);
        }
예제 #3
0
        public void TestAverage()
        {
            Vector2 mean = VecmathUtil.Average(new[] { new Vector2(0.5, 0.5), new Vector2(0.5, -0.5) });

            Assert.AreEqual(0.5, mean.X, 0.01);
            Assert.AreEqual(0, mean.Y, 0.01);
        }
예제 #4
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);
        }
예제 #5
0
        static Vector2 NewAttachPointAnnotationVector(IAtom atom, IEnumerable <IBond> bonds, List <Vector2> auxVectors)
        {
            if (!bonds.Any())
            {
                return(new Vector2(0, -1));
            }
            else if (bonds.Count() > 1)
            {
                return(NewAtomAnnotationVector(atom, bonds, auxVectors));
            }

            // only one bond (as expected)
            var bondVector = VecmathUtil.NewUnitVector(atom, bonds.First());
            var perpVector = VecmathUtil.NewPerpendicularVector(bondVector);

            // want the annotation below
            if (perpVector.Y > 0)
            {
                perpVector = -perpVector;
            }

            var vector = new Vector2((bondVector.X + perpVector.X) / 2,
                                     (bondVector.Y + perpVector.Y) / 2);

            vector = Vector2.Normalize(vector);
            return(vector);
        }
예제 #6
0
        public void TestToAwtPoint()
        {
            var p = VecmathUtil.ToPoint(new Vector2(4, 2));

            Assert.AreEqual(4, p.X, 0.01);
            Assert.AreEqual(2, p.Y, 0.01);
        }
예제 #7
0
        public void TestScale()
        {
            Vector2 vector = VecmathUtil.Scale(new Vector2(4, 2), 2.5);

            Assert.AreEqual(10, vector.X, 0.01);
            Assert.AreEqual(5, vector.Y, 0.01);
        }
예제 #8
0
        public void TestNewPerpendicularVector()
        {
            Vector2 perpendicular = VecmathUtil.NewPerpendicularVector(new Vector2(5, 2));

            Assert.AreEqual(-2, perpendicular.X, 0.01);
            Assert.AreEqual(5, perpendicular.Y, 0.01);
        }
예제 #9
0
        public void ParallelLines()
        {
            var intersect = VecmathUtil.Intersection(new Vector2(0, 1), new Vector2(0, 1), new Vector2(0, -1), new Vector2(0, 1));

            Assert.IsTrue(double.IsNaN(intersect.X));
            Assert.IsTrue(double.IsNaN(intersect.Y));
        }
예제 #10
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));
        }
예제 #11
0
        public void Intersection2()
        {
            var intersect = VecmathUtil.Intersection(new Vector2(6, 1), new Vector2(-4, -2), new Vector2(1, 6), new Vector2(2, 4));

            Assert.AreEqual(-4, intersect.X, 0.01);
            Assert.AreEqual(-4, intersect.Y, 0.01);
        }
예제 #12
0
        public void Intersection1()
        {
            var intersect = VecmathUtil.Intersection(new Vector2(1, 1), new Vector2(0, 1), new Vector2(1, 0), new Vector2(1, 0));

            Assert.AreEqual(1, intersect.X, 0.01);
            Assert.AreEqual(0, intersect.Y, 0.01);
        }
예제 #13
0
        public void TestToVecmathPoint()
        {
            Vector2 p = VecmathUtil.ToVector(new WPF::Point(4, 2));

            Assert.AreEqual(4, p.X, 0.01);
            Assert.AreEqual(2, p.Y, 0.01);
        }
예제 #14
0
        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);
        }
예제 #15
0
        public void TestNewUnitVector()
        {
            Vector2 unit = VecmathUtil.NewUnitVector(new Vector2(4, 2), new Vector2(6, 7));

            Assert.AreEqual(0.371, unit.X, 0.01);
            Assert.AreEqual(0.928, unit.Y, 0.01);
            Assert.AreEqual(1, unit.Length(), 0.01);
        }
예제 #16
0
        public void TestGetNearestVector2()
        {
            // not unit vectors, but okay for test
            Vector2 nearest = VecmathUtil.GetNearestVector(new Vector2(0, -1), new[] { new Vector2(0.5, 0.5), new Vector2(0.5, -0.5) });

            Assert.AreEqual(0.5, nearest.X, 0.01);
            Assert.AreEqual(-0.5, nearest.Y, 0.01);
        }
예제 #17
0
        /// <summary>
        /// Obtain the extents for a list of vectors.
        /// </summary>
        /// <param name="vectors">list of vectors</param>
        /// <returns>array of extents (not sorted)</returns>
        /// <seealso cref="Extent(Vector2)"/>
        public static double[] Extents(IList <Vector2> vectors)
        {
            int n = vectors.Count;

            double[] extents = new double[n];
            for (int i = 0; i < n; i++)
            {
                extents[i] = VecmathUtil.Extent(vectors[i]);
            }
            return(extents);
        }
예제 #18
0
        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);
        }
예제 #19
0
        public void TestNewUnitVectorFromBond()
        {
            var mock_a1 = new Mock <IAtom>(); var a1 = mock_a1.Object;
            var mock_a2 = new Mock <IAtom>(); var a2 = mock_a2.Object;

            mock_a1.Setup(n => n.Point2D).Returns(new Vector2(0, 1));
            mock_a2.Setup(n => n.Point2D).Returns(new Vector2(1, 0));
            var mock_bond = new Mock <IBond>(); var bond = mock_bond.Object;

            mock_bond.Setup(n => n.GetOther(a1)).Returns(a2);
            mock_bond.Setup(n => n.GetOther(a2)).Returns(a1);
            Vector2 unit = VecmathUtil.NewUnitVector(a1, bond);

            Assert.AreEqual(0.707, unit.X, 0.01);
            Assert.AreEqual(-0.707, unit.Y, 0.01);
            Assert.AreEqual(1, unit.Length(), 0.01);
        }
예제 #20
0
        private GeneralPath CreateRoundBracket(Vector2 p1, Vector2 p2, Vector2 perp, Vector2 midpoint)
        {
            var path = new PathGeometry();

            // bracket 1 (cp: control point)
            var pf = new PathFigure
            {
                StartPoint = new Point(p1.X + perp.X, p1.Y + perp.Y)
            };
            var cpb1 = midpoint + VecmathUtil.Negate(perp);
            var seg  = new QuadraticBezierSegment
            {
                Point1 = new Point(cpb1.X, cpb1.Y),
                Point2 = new Point(p2.X + perp.X, p2.Y + perp.Y)
            };

            pf.Segments.Add(seg);
            path.Figures.Add(pf);
            return(GeneralPath.OutlineOf(path, stroke, foreground));
        }
예제 #21
0
        public void TestNewUnitVectors()
        {
            var mock_fromAtom = new Mock <IAtom>(); var fromAtom = mock_fromAtom.Object;
            var mock_toAtom1 = new Mock <IAtom>(); var toAtom1 = mock_toAtom1.Object;
            var mock_toAtom2 = new Mock <IAtom>(); var toAtom2 = mock_toAtom2.Object;
            var mock_toAtom3 = new Mock <IAtom>(); var toAtom3 = mock_toAtom3.Object;

            mock_fromAtom.Setup(n => n.Point2D).Returns(new Vector2(4, 2));
            mock_toAtom1.Setup(n => n.Point2D).Returns(new Vector2(-5, 3));
            mock_toAtom2.Setup(n => n.Point2D).Returns(new Vector2(6, -4));
            mock_toAtom3.Setup(n => n.Point2D).Returns(new Vector2(7, 5));
            var vectors = VecmathUtil.NewUnitVectors(fromAtom, new[] { toAtom1, toAtom2, toAtom3 });

            Assert.AreEqual(3, vectors.Count);
            Assert.AreEqual(-0.993, vectors[0].X, 0.01);
            Assert.AreEqual(0.110, vectors[0].Y, 0.01);
            Assert.AreEqual(0.316, vectors[1].X, 0.01);
            Assert.AreEqual(-0.948, vectors[1].Y, 0.01);
            Assert.AreEqual(0.707, vectors[2].X, 0.01);
            Assert.AreEqual(0.707, vectors[2].Y, 0.01);
        }
예제 #22
0
        public void TestGetNearestVectorFromBonds()
        {
            var mock_a1 = new Mock <IAtom>(); var a1 = mock_a1.Object;
            var mock_a2 = new Mock <IAtom>(); var a2 = mock_a2.Object;
            var mock_a3 = new Mock <IAtom>(); var a3 = mock_a3.Object;
            var mock_a4 = new Mock <IAtom>(); var a4 = mock_a4.Object;
            var mock_b1 = new Mock <IBond>(); var b1 = mock_b1.Object;
            var mock_b2 = new Mock <IBond>(); var b2 = mock_b2.Object;
            var mock_b3 = new Mock <IBond>(); var b3 = mock_b3.Object;

            mock_b1.Setup(n => n.GetOther(a1)).Returns(a2);
            mock_b2.Setup(n => n.GetOther(a1)).Returns(a3);
            mock_b3.Setup(n => n.GetOther(a1)).Returns(a4);
            mock_a1.Setup(n => n.Point2D).Returns(new Vector2(0, 0));
            mock_a2.Setup(n => n.Point2D).Returns(new Vector2(0, 1));
            mock_a3.Setup(n => n.Point2D).Returns(new Vector2(1, 0));
            mock_a4.Setup(n => n.Point2D).Returns(new Vector2(1, 1)); // this one is found

            Vector2 nearest = VecmathUtil.GetNearestVector(new Vector2(0.5, 0.5), a1, new[] { b1, b2, b3 });

            Assert.AreEqual(0.707, nearest.X, 0.01);
            Assert.AreEqual(0.707, nearest.Y, 0.01);
        }
예제 #23
0
        private IRenderingElement GenerateSgroupBrackets(Sgroup sgroup,
                                                         IList <SgroupBracket> brackets,
                                                         IReadOnlyDictionary <IAtom, AtomSymbol> symbols,
                                                         string subscriptSuffix,
                                                         string superscriptSuffix)
        {
            // brackets are square by default (style:0)
            var  style  = (int?)sgroup.GetValue(SgroupKey.CtabBracketStyle);
            bool round  = style != null && style == 1;
            var  result = new ElementGroup();

            var atoms         = sgroup.Atoms;
            var crossingBonds = sgroup.Bonds;

            // easy to depict in correct orientation, we just
            // point each bracket at the atom of a crossing
            // bond that is 'in' the group - this scales
            // to more than two brackets

            // first we need to pair the brackets with the bonds
            var pairs = crossingBonds.Count == brackets.Count ? BracketBondPairs(brackets, crossingBonds) : Dictionaries.Empty <SgroupBracket, IBond>();

            // override bracket layout around single atoms to bring them in closer
            if (atoms.Count == 1)
            {
                var atom = atoms.First();

                // e.g. 2 HCL, 8 H2O etc.
                if (IsUnsignedInt(subscriptSuffix) &&
                    !crossingBonds.Any() &&
                    symbols.ContainsKey(atom))
                {
                    var prefix       = new TextOutline('·' + subscriptSuffix, font, emSize).Resize(1 / scale, 1 / -scale);
                    var prefixBounds = prefix.LogicalBounds;

                    var symbol = symbols[atom];

                    var bounds = symbol.GetConvexHull().Outline.Bounds;

                    // make slightly large
                    bounds = new Rect(bounds.Bottom - 2 * stroke,
                                      bounds.Left - 2 * stroke,
                                      bounds.Width + 4 * stroke,
                                      bounds.Height + 4 * stroke);

                    prefix = prefix.Translate(bounds.Bottom - prefixBounds.Top,
                                              symbol.GetAlignmentCenter().Y - prefixBounds.CenterY());

                    result.Add(GeneralPath.ShapeOf(prefix.GetOutline(), foreground));
                }
                // e.g. CC(O)nCC
                else if (crossingBonds.Count > 0)
                {
                    var scriptscale = labelScale;

                    var leftBracket  = new TextOutline("(", font, emSize).Resize(1 / scale, 1 / -scale);
                    var rightBracket = new TextOutline(")", font, emSize).Resize(1 / scale, 1 / -scale);

                    var leftCenter  = leftBracket.GetCenter();
                    var rightCenter = rightBracket.GetCenter();

                    if (symbols.ContainsKey(atom))
                    {
                        var symbol = symbols[atom];

                        var bounds = symbol.GetConvexHull().Outline.Bounds;
                        // make slightly large
                        bounds = new Rect(bounds.Left - 2 * stroke,
                                          bounds.Top - 2 * stroke,
                                          bounds.Width + 4 * stroke,
                                          bounds.Height + 4 * stroke);

                        leftBracket = leftBracket.Translate(bounds.Left - 0.1 - leftCenter.X,
                                                            symbol.GetAlignmentCenter().Y - leftCenter.Y);
                        rightBracket = rightBracket.Translate(bounds.Right + 0.1 - rightCenter.X,
                                                              symbol.GetAlignmentCenter().Y - rightCenter.Y);
                    }
                    else
                    {
                        var p = atoms.First().Point2D.Value;
                        leftBracket  = leftBracket.Translate(p.X - 0.2 - leftCenter.X, p.Y - leftCenter.Y);
                        rightBracket = rightBracket.Translate(p.X + 0.2 - rightCenter.X, p.Y - rightCenter.Y);
                    }

                    result.Add(GeneralPath.ShapeOf(leftBracket.GetOutline(), foreground));
                    result.Add(GeneralPath.ShapeOf(rightBracket.GetOutline(), foreground));

                    var rightBracketBounds = rightBracket.GetBounds();

                    // subscript/superscript suffix annotation
                    if (subscriptSuffix != null && subscriptSuffix.Any())
                    {
                        TextOutline subscriptOutline = LeftAlign(MakeText(subscriptSuffix.ToLowerInvariant(),
                                                                          new Vector2(rightBracketBounds.Right,
                                                                                      rightBracketBounds.Top - 0.1),
                                                                          new Vector2(-0.5 * rightBracketBounds.Width, 0),
                                                                          scriptscale));
                        result.Add(GeneralPath.ShapeOf(subscriptOutline.GetOutline(), foreground));
                    }
                    if (superscriptSuffix != null && superscriptSuffix.Any())
                    {
                        var superscriptOutline = LeftAlign(MakeText(superscriptSuffix.ToLowerInvariant(),
                                                                    new Vector2(rightBracketBounds.Right,
                                                                                rightBracketBounds.Bottom + 0.1),
                                                                    new Vector2(-rightBracketBounds.Width, 0),
                                                                    scriptscale));
                        result.Add(GeneralPath.ShapeOf(superscriptOutline.GetOutline(), foreground));
                    }
                }
            }
            else if (pairs.Any())
            {
                SgroupBracket suffixBracket     = null;
                Vector2?      suffixBracketPerp = null;

                foreach (var e in pairs)
                {
                    var bracket     = e.Key;
                    var bond        = e.Value;
                    var inGroupAtom = atoms.Contains(bond.Begin) ? bond.Begin : bond.End;

                    var p1 = bracket.FirstPoint;
                    var p2 = bracket.SecondPoint;

                    var perp = VecmathUtil.NewPerpendicularVector(VecmathUtil.NewUnitVector(p1, p2));

                    // point the vector at the atom group
                    var midpoint = VecmathUtil.Midpoint(p1, p2);
                    if (Vector2.Dot(perp, VecmathUtil.NewUnitVector(midpoint, inGroupAtom.Point2D.Value)) < 0)
                    {
                        perp = Vector2.Negate(perp);
                    }
                    perp *= bracketDepth;

                    if (round)
                    {
                        result.Add(CreateRoundBracket(p1, p2, perp, midpoint));
                    }
                    else
                    {
                        result.Add(CreateSquareBracket(p1, p2, perp));
                    }

                    if (suffixBracket == null)
                    {
                        suffixBracket     = bracket;
                        suffixBracketPerp = perp;
                    }
                    else
                    {
                        // is this bracket better as a suffix?
                        var sp1      = suffixBracket.FirstPoint;
                        var sp2      = suffixBracket.SecondPoint;
                        var bestMaxX = Math.Max(sp1.X, sp2.X);
                        var thisMaxX = Math.Max(p1.X, p2.X);
                        var bestMaxY = Math.Max(sp1.Y, sp2.Y);
                        var thisMaxY = Math.Max(p1.Y, p2.Y);

                        // choose the most eastern or.. the most southern
                        var xDiff = thisMaxX - bestMaxX;
                        var yDiff = thisMaxY - bestMaxY;
                        if (xDiff > EQUIV_THRESHOLD || (xDiff > -EQUIV_THRESHOLD && yDiff < -EQUIV_THRESHOLD))
                        {
                            suffixBracket     = bracket;
                            suffixBracketPerp = perp;
                        }
                    }
                }

                // write the labels
                if (suffixBracket != null)
                {
                    var subSufPnt = suffixBracket.FirstPoint;
                    var supSufPnt = suffixBracket.SecondPoint;

                    // try to put the subscript on the bottom
                    var xDiff = subSufPnt.X - supSufPnt.X;
                    var yDiff = subSufPnt.Y - supSufPnt.Y;
                    if (yDiff > EQUIV_THRESHOLD || (yDiff > -EQUIV_THRESHOLD && xDiff > EQUIV_THRESHOLD))
                    {
                        var tmpP = subSufPnt;
                        subSufPnt = supSufPnt;
                        supSufPnt = tmpP;
                    }

                    // subscript/superscript suffix annotation
                    if (subscriptSuffix != null && subscriptSuffix.Any())
                    {
                        var subscriptOutline = LeftAlign(MakeText(subscriptSuffix.ToLowerInvariant(), subSufPnt, suffixBracketPerp.Value, labelScale));
                        result.Add(GeneralPath.ShapeOf(subscriptOutline.GetOutline(), foreground));
                    }
                    if (superscriptSuffix != null && superscriptSuffix.Any())
                    {
                        var superscriptOutline = LeftAlign(MakeText(superscriptSuffix.ToLowerInvariant(), supSufPnt, suffixBracketPerp.Value, labelScale));
                        result.Add(GeneralPath.ShapeOf(superscriptOutline.GetOutline(), foreground));
                    }
                }
            }
            else if (brackets.Count == 2)
            {
                var b1p1 = brackets[0].FirstPoint;
                var b1p2 = brackets[0].SecondPoint;
                var b2p1 = brackets[1].FirstPoint;
                var b2p2 = brackets[1].SecondPoint;

                var b1vec = VecmathUtil.NewUnitVector(b1p1, b1p2);
                var b2vec = VecmathUtil.NewUnitVector(b2p1, b2p2);

                var b1pvec = VecmathUtil.NewPerpendicularVector(b1vec);
                var b2pvec = VecmathUtil.NewPerpendicularVector(b2vec);

                // Point the vectors at each other
                if (Vector2.Dot(b1pvec, VecmathUtil.NewUnitVector(b1p1, b2p1)) < 0)
                {
                    b1pvec = Vector2.Negate(b1pvec);
                }
                if (Vector2.Dot(b2pvec, VecmathUtil.NewUnitVector(b2p1, b1p1)) < 0)
                {
                    b2pvec = Vector2.Negate(b2pvec);
                }

                // scale perpendicular vectors by how deep the brackets need to be
                b1pvec *= bracketDepth;
                b2pvec *= bracketDepth;

                // bad brackets
                if (double.IsNaN(b1pvec.X) || double.IsNaN(b1pvec.Y) ||
                    double.IsNaN(b2pvec.X) || double.IsNaN(b2pvec.Y))
                {
                    return(result);
                }

                {
                    var path = new PathGeometry();

                    if (round)
                    {
                        {
                            // bracket 1 (cp: control point)
                            var pf = new PathFigure
                            {
                                StartPoint = new Point(b1p1.X + b1pvec.X, b1p1.Y + b1pvec.Y)
                            };
                            Vector2 cpb1 = VecmathUtil.Midpoint(b1p1, b1p2);
                            cpb1 += VecmathUtil.Negate(b1pvec);
                            var seg = new QuadraticBezierSegment
                            {
                                Point1 = new Point(cpb1.X, cpb1.Y),
                                Point2 = new Point(b1p2.X + b1pvec.X, b1p2.Y + b1pvec.Y)
                            };
                            pf.Segments.Add(seg);
                            path.Figures.Add(pf);
                        }

                        {
                            // bracket 2 (cp: control point)
                            var pf = new PathFigure
                            {
                                StartPoint = new Point(b2p1.X + b2pvec.X, b2p1.Y + b2pvec.Y)
                            };
                            var cpb2 = VecmathUtil.Midpoint(b2p1, b2p2);
                            cpb2 += VecmathUtil.Negate(b2pvec);
                            var seg = new QuadraticBezierSegment
                            {
                                Point1 = new Point(cpb2.X, cpb2.Y),
                                Point2 = new Point(b2p2.X + b2pvec.X, b2p2.Y + b2pvec.Y)
                            };
                            pf.Segments.Add(seg);
                            path.Figures.Add(pf);
                        }
                    }
                    else
                    {
                        {
                            // bracket 1
                            var pf = new PathFigure
                            {
                                StartPoint = new Point(b1p1.X + b1pvec.X, b1p1.Y + b1pvec.Y)
                            };
                            var seg = new PolyLineSegment();
                            seg.Points.Add(new Point(b1p1.X, b1p1.Y));
                            seg.Points.Add(new Point(b1p2.X, b1p2.Y));
                            seg.Points.Add(new Point(b1p2.X + b1pvec.X, b1p2.Y + b1pvec.Y));
                            pf.Segments.Add(seg);
                            path.Figures.Add(pf);
                        }

                        {
                            // bracket 2
                            var pf = new PathFigure
                            {
                                StartPoint = new Point(b2p1.X + b2pvec.X, b2p1.Y + b2pvec.Y)
                            };
                            var seg = new PolyLineSegment();
                            seg.Points.Add(new Point(b2p1.X, b2p1.Y));
                            seg.Points.Add(new Point(b2p2.X, b2p2.Y));
                            seg.Points.Add(new Point(b2p2.X + b2pvec.X, b2p2.Y + b2pvec.Y));
                            pf.Segments.Add(seg);
                            path.Figures.Add(pf);
                        }
                    }

                    result.Add(GeneralPath.OutlineOf(path, stroke, foreground));
                }

                // work out where to put the suffix labels (e.g. ht/hh/eu) superscript
                // and (e.g. n, xl, c, mix) subscript
                // TODO: could be improved
                var b1MaxX = Math.Max(b1p1.X, b1p2.X);
                var b2MaxX = Math.Max(b2p1.X, b2p2.X);
                var b1MaxY = Math.Max(b1p1.Y, b1p2.Y);
                var b2MaxY = Math.Max(b2p1.Y, b2p2.Y);

                var subSufPnt = b2p2;
                var supSufPnt = b2p1;
                var subpvec   = b2pvec;

                var bXDiff = b1MaxX - b2MaxX;
                var bYDiff = b1MaxY - b2MaxY;

                if (bXDiff > EQUIV_THRESHOLD || (bXDiff > -EQUIV_THRESHOLD && bYDiff < -EQUIV_THRESHOLD))
                {
                    subSufPnt = b1p2;
                    supSufPnt = b1p1;
                    subpvec   = b1pvec;
                }

                var xDiff = subSufPnt.X - supSufPnt.X;
                var yDiff = subSufPnt.Y - supSufPnt.Y;

                if (yDiff > EQUIV_THRESHOLD || (yDiff > -EQUIV_THRESHOLD && xDiff > EQUIV_THRESHOLD))
                {
                    var tmpP = subSufPnt;
                    subSufPnt = supSufPnt;
                    supSufPnt = tmpP;
                }

                // subscript/superscript suffix annotation
                if (subscriptSuffix != null && subscriptSuffix.Any())
                {
                    var subscriptOutline = LeftAlign(MakeText(subscriptSuffix.ToLowerInvariant(), subSufPnt, subpvec, labelScale));
                    result.Add(GeneralPath.ShapeOf(subscriptOutline.GetOutline(), foreground));
                }
                if (superscriptSuffix != null && superscriptSuffix.Any())
                {
                    var superscriptOutline = LeftAlign(MakeText(superscriptSuffix.ToLowerInvariant(), supSufPnt, subpvec, labelScale));
                    result.Add(GeneralPath.ShapeOf(superscriptOutline.GetOutline(), foreground));
                }
            }
            return(result);
        }
예제 #24
0
 private TextOutline MakeText(string subscriptSuffix, Vector2 b1p2, Vector2 b1pvec, double labelScale)
 {
     return(StandardGenerator.GenerateAnnotation(b1p2, subscriptSuffix, VecmathUtil.Negate(b1pvec), 1, labelScale, font, emSize, null).Resize(1 / scale, 1 / scale));
 }
예제 #25
0
        public void TestAdjacentLength()
        {
            double length = VecmathUtil.AdjacentLength(new Vector2(2, 4), new Vector2(9, 4), 6d);

            Assert.AreEqual(4.94, length, 0.01);
        }
예제 #26
0
 public void TestGetNearestVectorComplainsWhenNoVectorsProvided()
 {
     VecmathUtil.GetNearestVector(new Vector2(1, 0), Array.Empty <Vector2>());
 }
예제 #27
0
        /// <summary>
        /// Using the angular extents of vectors, determine the best position for a hydrogen label. The
        /// position with the most space is selected first. If multiple positions have the same amount of
        /// space, the one where the hydrogen position is most centred is selected. If all position are
        /// okay, the priority is Right > Left > Above > Below.
        /// </summary>
        /// <param name="vectors">directional vectors for each bond from an atom</param>
        /// <returns>best hydrogen position</returns>
        internal static HydrogenPosition UsingAngularExtent(IList <Vector2> vectors)
        {
            var extents = VecmathUtil.Extents(vectors);

            Array.Sort(extents);

            var extentMap = new Dictionary <HydrogenPosition, OffsetExtent>();

            for (int i = 0; i < extents.Length; i++)
            {
                double before = extents[i];
                double after  = extents[(i + 1) % extents.Length];

                foreach (var position in Values)
                {
                    // adjust the extents such that this position is '0'
                    double bias       = Tau - V[(int)position].Direction;
                    double afterBias  = after + bias;
                    double beforeBias = before + bias;

                    // ensure values are 0 <= x < Tau
                    if (beforeBias >= Tau)
                    {
                        beforeBias -= Tau;
                    }
                    if (afterBias >= Tau)
                    {
                        afterBias -= Tau;
                    }

                    // we can now determine the extents before and after this
                    // hydrogen position
                    double afterExtent  = afterBias;
                    double beforeExtent = Tau - beforeBias;

                    // the total extent is amount of space between these two bonds
                    // when sweeping round. The offset is how close this hydrogen
                    // position is to the center of the extent.
                    double totalExtent = afterExtent + beforeExtent;
                    double offset      = Math.Abs(totalExtent / 2 - beforeExtent);

                    // for each position keep the one with the smallest extent this is
                    // the most space available without another bond getting in the way
                    if (!extentMap.TryGetValue(position, out OffsetExtent offsetExtent) || totalExtent < offsetExtent.Extent)
                    {
                        extentMap[position] = new OffsetExtent(totalExtent, offset);
                    }
                }
            }

            // we now have the offset extent for each position that we can sort and prioritise
            var extentEntries = extentMap;
            KeyValuePair <HydrogenPosition, OffsetExtent>?best = null;

            foreach (var e in extentEntries)
            {
                if (best == null || ExtentPriority.Instance.Compare(e, best.Value) < 0)
                {
                    best = e;
                }
            }

            Debug.Assert(best != null);
            return(best.Value.Key);
        }
예제 #28
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));
            }
        }
예제 #29
0
 public void SweepWest()
 {
     Assert.AreEqual(Vectors.DegreeToRadian(180), VecmathUtil.Extent(new Vector2(-1, 0)), 0.01);
 }
예제 #30
0
 public void SweepSouth()
 {
     Assert.AreEqual(Vectors.DegreeToRadian(270), VecmathUtil.Extent(new Vector2(0, -1)), 0.01);
 }