private void btn3DLinksBrainBrain_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                PrepFor3D();
                ClearTempVisuals();

                if (_brains3D.Count == 0)
                {
                    return;
                }

                LinkItem[] brains = _brains3D.
                    Select(o => new LinkItem(o.Position.Value, o.Size)).
                    ToArray();

                ItemLinker_CombineArgs combineArgs = new ItemLinker_CombineArgs()
                {
                    //Ratio_Skinny = .3,
                    //Ratio_Wide = .88,
                    //MergeChance = 0,
                };

                SortedList<Tuple<int, int>, double> all;
                LinkSetPair[] final;
                ItemLinker.Link_Self(out all, out final, brains, combineArgs);

                #region Draw

                if (final != null)
                {
                    foreach (var link in final)
                    {
                        // Group From
                        if (link.Set1.Items.Length > 1)
                        {
                            _linksBrain3D.Add(DrawBrainGroup(_viewportFull, link.Set1.Positions, link.Set2.Center, _brainGroupColor));
                        }

                        // Group To
                        if (link.Set2.Items.Length > 1)
                        {
                            _linksBrain3D.Add(DrawBrainGroup(_viewportFull, link.Set2.Positions, link.Set2.Center, _brainGroupColor));
                        }

                        // Link
                        Visual3D visual = AddLine(_viewportFull, link.Set1.Center, link.Set2.Center, _brainLinkColor);

                        _linksBrain3D.Add(new Item3D(visual, 0, new[] { link.Set1.Center, link.Set2.Center }));
                    }
                }

                #endregion
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString(), this.Title, MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
        private static LinkSetPair[] PruneLinks(TriangleIndexed[] triangles, SortedList<Tuple<int, int>, double> all, LinkItem[] brains, ItemLinker_CombineArgs args)
        {
            List<Tuple<int[], int[]>> retVal = all.Keys.
                Select(o => Tuple.Create(new[] { o.Item1 }, new[] { o.Item2 })).
                ToList();

            foreach (TriangleIndexed triangle in triangles)
            {
                Tuple<bool, TriangleEdge> changeEdge = PruneLinks_LongThin(triangle, all, args);
                if (changeEdge == null)
                {
                    continue;
                }

                if (changeEdge.Item1)
                {
                    PruneLinks_Merge(triangle, changeEdge.Item2, retVal);
                }
                else
                {
                    PruneLinks_Remove(triangle, changeEdge.Item2, retVal);
                }
            }

            return retVal.Select(o => new LinkSetPair(o.Item1, o.Item2, brains)).ToArray();
        }
        /// <summary>
        /// If this triangle is long and thin, then this will decide whether to remove a link, or merge the two close brains
        /// </summary>
        /// <returns>
        /// Null : This is not a long thin triangle.  Move along
        /// Item1=True : Merge the two brains connected by Item2
        /// Item1=False : Remove the Item2 link
        /// </returns>
        private static Tuple<bool, TriangleEdge> PruneLinks_LongThin(ITriangleIndexed triangle, SortedList<Tuple<int, int>, double> all, ItemLinker_CombineArgs args)
        {
            var lengths = new[] { TriangleEdge.Edge_01, TriangleEdge.Edge_12, TriangleEdge.Edge_20 }.
                Select(o => new { Edge = o, Length = GetLength(triangle, o, all) }).
                OrderBy(o => o.Length).
                ToArray();

            //NOTE: The order of these if statements is important.  I ran into a case where it's both wide and
            //skinny (nearly colinear).  In that case, the long segment should be removed
            if (lengths[2].Length / (lengths[0].Length + lengths[1].Length) > args.Ratio_Wide)
            {
                // Wide base (small angles, one huge angle)
                return Tuple.Create(false, lengths[2].Edge);
            }
            else if (lengths[0].Length / lengths[1].Length < args.Ratio_Skinny && lengths[0].Length / lengths[2].Length < args.Ratio_Skinny)
            {
                #region Isosceles - skinny base

                if (StaticRandom.NextDouble() < args.MergeChance)
                {
                    // Treat the two close brains like one, and split the links evenly with the far brain
                    return Tuple.Create(true, lengths[0].Edge);
                }
                else
                {
                    // Choose one of the long links to remove
                    if (StaticRandom.NextBool())
                    {
                        return Tuple.Create(false, lengths[1].Edge);
                    }
                    else
                    {
                        return Tuple.Create(false, lengths[2].Edge);
                    }
                }

                #endregion
            }

            return null;
        }
        /// <summary>
        /// Link brains to each other (delaunay graph, then prune thin triangles)
        /// </summary>
        /// <param name="all">
        /// Item1=Link between two items (sub item1 and 2 are the indices)
        /// Item2=Distance between those two items
        /// </param>
        /// <param name="final">
        /// This holds a set of links after the thin triangles are pruned.  There's a chance of items being merged
        /// </param>
        public static void Link_Self(out SortedList<Tuple<int, int>, double> all, out LinkSetPair[] final, LinkItem[] items, ItemLinker_CombineArgs combineArgs = null)
        {
            TriangleIndexed[] triangles;
            GetItemDelaunay(out all, out triangles, items);

            if (items.Length < 2)
            {
                //throw new ArgumentException("This method requires at least two brains: " + items.Length.ToString());
                final = new LinkSetPair[0];
                return;
            }

            // Prune links that don't make sense
            if (combineArgs != null && triangles.Length > 0)
            {
                final = PruneLinks(triangles, all, items, combineArgs);
            }
            else
            {
                final = all.Keys.
                    Select(o => new LinkSetPair(o.Item1, o.Item2, items)).
                    ToArray();
            }
        }