/// <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))); }
/// <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); }