/// <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))); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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)); }
/// <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)); }
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); }
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); }
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); }
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 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); }
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); }
/// <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); }
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); }
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); }
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)); }
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); }
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); }
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); }
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)); }
public void TestAdjacentLength() { double length = VecmathUtil.AdjacentLength(new Vector2(2, 4), new Vector2(9, 4), 6d); Assert.AreEqual(4.94, length, 0.01); }
public void TestGetNearestVectorComplainsWhenNoVectorsProvided() { VecmathUtil.GetNearestVector(new Vector2(1, 0), Array.Empty <Vector2>()); }
/// <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); }
/// <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)); } }
public void SweepWest() { Assert.AreEqual(Vectors.DegreeToRadian(180), VecmathUtil.Extent(new Vector2(-1, 0)), 0.01); }
public void SweepSouth() { Assert.AreEqual(Vectors.DegreeToRadian(270), VecmathUtil.Extent(new Vector2(0, -1)), 0.01); }