private static Visual3D GetHoverVisual(Axis3DProps axis, Bar3DProps bar, bool isHover)
        {
            //TODO:
            //  probably a flattened semitransparent dome over top and bottom of bar
            //  or maybe a semitransparent plate that hovers over top and bottom of bar

            double halfBarSize = axis.BarSize / 2d;

            double x = -axis.HalfX + (bar.X * axis.BarSize) + halfBarSize;
            double y = axis.HalfY - (bar.Y * axis.BarSize) - halfBarSize;

            double zPos = axis.AxisOffset + (axis.ZHeight * 1.5);
            double zNeg;
            if (axis.AxisOffset.IsNearZero())
            {
                zNeg = -zPos;
            }
            else
            {
                zNeg = axis.AxisOffset - (axis.ZHeight * .1);
            }

            ScreenSpaceLines3D retVal = new ScreenSpaceLines3D();

            if (isHover)
            {
                retVal.Color = UtilityWPF.AlphaBlend(Colors.LimeGreen, Colors.Gray, .33);
                retVal.Thickness = 1.5;
            }
            else
            {
                retVal.Color = Colors.LimeGreen;
                retVal.Thickness = 4;
            }

            retVal.AddLine(new Point3D(x, y, zNeg), new Point3D(x, y, zPos));

            return retVal;
        }
        private Bar3DProps[] GetSurroundingBars(Bar3DProps bar, int brushSize, bool isCircle = true)
        {
            int half = brushSize / 2;
            if (brushSize % 2 == 0)
            {
                half--;     // without this, the box will be shifted up/left one more than is intuitive (the rectange will still be the same size)
            }

            Point center = brushSize % 2 == 0 ?
                new Point(bar.X + 1, bar.Y + 1) :       // the center of the circle sits at the corner one pixel over
                new Point(bar.X + .5, bar.Y + .5);      // the center of the circle sits at the center of a pixel (pixel width of one)
            double radiusSquared = (brushSize / 2d) * (brushSize / 2d);

            RectInt rect = RectInt.Intersect(new RectInt(bar.X - half, bar.Y - half, brushSize, brushSize), new RectInt(_width, _height)).Value;

            List<Bar3DProps> retVal = new List<Bar3DProps>();

            for (int y = rect.Top; y < rect.Bottom; y++)
            {
                int offsetY = y * _width;

                for (int x = rect.Left; x < rect.Right; x++)
                {
                    if (isCircle && (center - new Point(x + .5, y + .5)).LengthSquared > radiusSquared)
                    {
                        continue;
                    }

                    Bar3DProps barAdd = _bars.Bars[offsetY + x];
                    if (barAdd.X != x || barAdd.Y != y)
                    {
                        throw new ApplicationException("Bar isn't where it should be");
                    }

                    retVal.Add(barAdd);
                }
            }

            return retVal.ToArray();
        }
        private static Bar3DProps RebuildBars_Bar(Axis3DProps axis, AllBar3D bars, int index, int x, int y, double value)
        {
            Bar3DProps retVal = new Bar3DProps(axis, bars, index, x, y);

            retVal.Model = new GeometryModel3D();

            //NOTE: Material is updated by bars.UpdateColors

            double xPos = -axis.HalfX + (x * axis.BarSize);
            double yPos = axis.HalfY - (y * axis.BarSize);

            retVal.Model.Geometry = UtilityWPF.GetCube(new Point3D(xPos, yPos, 0), new Point3D(xPos + axis.BarSize, yPos - axis.BarSize, 1));

            Transform3DGroup transform = new Transform3DGroup();
            retVal.HeightTransform = new ScaleTransform3D(1, 1, 1);
            transform.Children.Add(retVal.HeightTransform);
            transform.Children.Add(new TranslateTransform3D(0, 0, axis.AxisOffset));
            retVal.Model.Transform = transform;

            retVal.Height = value;

            return retVal;
        }
        private void RebuildBars_WRONGY()
        {
            if (_axis == null)
            {
                return;
            }

            if (_bars != null)
            {
                _viewport.Children.Remove(_bars.Visual);
                _bars = null;
            }

            AllBar3D bars = new AllBar3D();

            bars.Models = new Model3DGroup();

            List<Bar3DProps> individualBars = new List<Bar3DProps>();

            for (int y = 0; y < _height; y++)
            {
                int indexOffset = y * _width;

                for (int x = 0; x < _width; x++)
                {
                    Bar3DProps bar = new Bar3DProps(_axis, bars, indexOffset + x, x, y);

                    bar.Model = new GeometryModel3D();

                    //NOTE: Material is updated by bar.Height set

                    double xPos = -_axis.HalfX + (x * _axis.BarSize);
                    double yPos = -_axis.HalfY + (y * _axis.BarSize);

                    bar.Model.Geometry = UtilityWPF.GetCube(new Point3D(xPos, yPos, 0), new Point3D(xPos + _axis.BarSize, yPos + _axis.BarSize, 1));

                    Transform3DGroup transform = new Transform3DGroup();
                    bar.HeightTransform = new ScaleTransform3D(1, 1, 1);
                    transform.Children.Add(bar.HeightTransform);
                    transform.Children.Add(new TranslateTransform3D(0, 0, _axis.AxisOffset));
                    bar.Model.Transform = transform;

                    bar.Height = _values[indexOffset + x];

                    bars.Models.Children.Add(bar.Model);
                    individualBars.Add(bar);
                }
            }

            bars.Bars = individualBars.ToArray();



            ModelVisual3D visual = new ModelVisual3D();
            visual.Content = bars.Models;
            bars.Visual = visual;

            _bars = bars;
            _viewport.Children.Add(_bars.Visual);
        }
        private void RemoveSelected(Bar3DProps bar)
        {
            for (int cntr = 0; cntr < _bars.Selected.Count; cntr++)
            {
                if (_bars.Selected[cntr].Item1 == bar.Index)
                {
                    _viewport.Children.Remove(_bars.Selected[cntr].Item2);
                    _bars.Selected.RemoveAt(cntr);
                    break;
                }
            }

            if (_bars.Selected.Count == 0)
            {
                DisableSelectedControls();
            }
        }
        private void AddSelected(Bar3DProps bar)
        {
            // Get a new visual
            Visual3D visual = GetHoverVisual(_axis, bar, false);

            // Store it
            _viewport.Children.Add(visual);
            _bars.Selected.Add(Tuple.Create(bar.Index, visual));

            #region Update controls

            _isProgramaticallyChangingSettings = true;

            // Textbox
            txtHeight3D.Text = bar.Height.ToStringSignificantDigits(3);

            // Slider
            if (radRangeZeroPos.IsChecked.Value)
            {
                trkHeight3D.Minimum = 0;
                trkHeight3D.Maximum = 1;
            }
            else
            {
                trkHeight3D.Minimum = -1;
                trkHeight3D.Maximum = 1;
            }

            trkHeight3D.Value = bar.Height;

            grdSettings3D.IsEnabled = true;

            _isProgramaticallyChangingSettings = false;

            #endregion
        }
        private void SelectUnselect(Bar3DProps overBar)
        {
            foreach (Bar3DProps bar in GetSurroundingBars(overBar, trkBrushSize.Value.ToInt_Round()))
            {
                bool isSelected = _bars.Selected.Any(o => o.Item1 == bar.Index);

                if (_isDragRemoving && isSelected)
                {
                    RemoveSelected(bar);
                }
                else if (!_isDragRemoving && !isSelected)
                {
                    AddSelected(bar);
                }
            }
        }