private (int edge, int otherSide) GetHitIndices(IObject3D selectedItem)
        {
            var bestZEdgePosition = -1;
            var otherSide         = -1;
            var bestCornerZ       = double.PositiveInfinity;

            // get the closest z on the bottom in view space
            for (int i = 0; i < 4; i++)
            {
                Vector3 cornerPosition    = ObjectSpace.GetEdgePosition(selectedItem, i, placement);
                Vector3 cornerScreenSpace = Object3DControlContext.World.WorldToScreenSpace(cornerPosition);
                if (cornerScreenSpace.Z < bestCornerZ)
                {
                    bestCornerZ       = cornerScreenSpace.Z;
                    bestZEdgePosition = i;
                    otherSide         = (i + 2) % 4;
                }
            }

            return(bestZEdgePosition, otherSide);
        }
        private Vector3 GetDeltaToOtherSideXy(IObject3D selectedItem, int quadrantIndex)
        {
            Vector3 cornerPosition    = ObjectSpace.GetCornerPosition(selectedItem, quadrantIndex);
            Vector3 cornerPositionCcw = ObjectSpace.GetCornerPosition(selectedItem, quadrantIndex + 1);
            Vector3 cornerPositionCw  = ObjectSpace.GetCornerPosition(selectedItem, quadrantIndex + 3);

            double xDirection = cornerPositionCcw.X - cornerPosition.X;

            if (xDirection == 0)
            {
                xDirection = cornerPositionCw.X - cornerPosition.X;
            }

            double yDirection = cornerPositionCcw.Y - cornerPosition.Y;

            if (yDirection == 0)
            {
                yDirection = cornerPositionCw.Y - cornerPosition.Y;
            }

            return(new Vector3(xDirection, yDirection, cornerPosition.Z));
        }
        public override void SetPosition(IObject3D selectedItem, MeshSelectInfo selectInfo)
        {
            // create the transform for the box
            Vector3 edgePosition = ObjectSpace.GetEdgePosition(selectedItem, edgeIndex);

            Vector3 boxCenter = edgePosition;

            double distBetweenPixelsWorldSpace = Object3DControlContext.World.GetWorldUnitsPerScreenPixelAtPosition(edgePosition);

            switch (edgeIndex)
            {
            case 0:
                boxCenter.Y += selectCubeSize / 2 * distBetweenPixelsWorldSpace;
                break;

            case 1:
                boxCenter.X -= selectCubeSize / 2 * distBetweenPixelsWorldSpace;
                break;

            case 2:
                boxCenter.Y -= selectCubeSize / 2 * distBetweenPixelsWorldSpace;
                break;

            case 3:
                boxCenter.X += selectCubeSize / 2 * distBetweenPixelsWorldSpace;
                break;
            }

            boxCenter.Z += selectCubeSize / 2 * distBetweenPixelsWorldSpace;

            var rotation = Matrix4X4.CreateRotation(new Quaternion(selectedItem.Matrix));

            var centerMatrix = Matrix4X4.CreateTranslation(boxCenter);

            centerMatrix   = rotation * Matrix4X4.CreateScale(distBetweenPixelsWorldSpace) * centerMatrix;
            TotalTransform = centerMatrix;
        }
        public override async void OnMouseMove(Mouse3DEventArgs mouseEvent3D, bool mouseIsOver)
        {
            var selectedItem = RootSelection;

            ActiveSelectedItem = selectedItem;

            if (MouseIsOver || MouseDownOnControl)
            {
                xValueDisplayInfo.Visible = true;
                yValueDisplayInfo.Visible = true;
            }
            else if (!hadClickOnControl || scaleController.HasChange)
            {
                xValueDisplayInfo.Visible = false;
                yValueDisplayInfo.Visible = false;
            }

            if (MouseDownOnControl && hitPlane != null)
            {
                var info = hitPlane.GetClosestIntersection(mouseEvent3D.MouseRay);

                if (info != null &&
                    selectedItem != null)
                {
                    var lockedEdge = ObjectSpace.GetCornerPosition(selectedItem, quadrantIndex + 2);

                    var delta = info.HitPosition - initialHitPosition;

                    var corner0     = ObjectSpace.GetCornerPosition(selectedItem, quadrantIndex);
                    var corner1     = ObjectSpace.GetCornerPosition(selectedItem, quadrantIndex + 1);
                    var corner3     = ObjectSpace.GetCornerPosition(selectedItem, quadrantIndex + 3);
                    var direction01 = (corner0 - corner1).GetNormal();
                    var direction03 = (corner0 - corner3).GetNormal();

                    var deltaAlong01 = direction01.Dot(delta);
                    var deltaAlong03 = direction03.Dot(delta);

                    // scale it
                    var newSize = new Vector2(scaleController.InitialState.Width, scaleController.InitialState.Depth);
                    if (quadrantIndex % 2 == 0)
                    {
                        newSize.X += deltaAlong01;
                        newSize.X  = Math.Max(Math.Max(newSize.X, .001), Object3DControlContext.SnapGridDistance);

                        newSize.Y += deltaAlong03;
                        newSize.Y  = Math.Max(Math.Max(newSize.Y, .001), Object3DControlContext.SnapGridDistance);
                    }
                    else
                    {
                        newSize.X += deltaAlong03;
                        newSize.X  = Math.Max(Math.Max(newSize.X, .001), Object3DControlContext.SnapGridDistance);

                        newSize.Y += deltaAlong01;
                        newSize.Y  = Math.Max(Math.Max(newSize.Y, .001), Object3DControlContext.SnapGridDistance);
                    }

                    if (Object3DControlContext.SnapGridDistance > 0)
                    {
                        // snap this position to the grid
                        double snapGridDistance = Object3DControlContext.SnapGridDistance;

                        // snap this position to the grid
                        newSize.X = ((int)((newSize.X / snapGridDistance) + .5)) * snapGridDistance;
                        newSize.Y = ((int)((newSize.Y / snapGridDistance) + .5)) * snapGridDistance;
                    }

                    scaleController.ScaleWidthDepth(newSize.X, newSize.Y);

                    await selectedItem.Rebuild();

                    // and keep the locked edge in place
                    var newLockedEdge = ObjectSpace.GetCornerPosition(selectedItem, quadrantIndex + 2);

                    selectedItem.Matrix *= Matrix4X4.CreateTranslation(lockedEdge - newLockedEdge);

                    Invalidate();
                }
            }

            base.OnMouseMove(mouseEvent3D, mouseIsOver);
        }
        public ScaleDiameterControl(IObject3DControlContext context,
                                    Func <double> getHeight,
                                    Action <double> setHeight,
                                    List <Func <double> > getDiameters,
                                    List <Action <double> > setDiameters,
                                    int diameterIndex,
                                    ObjectSpace.Placement placement = ObjectSpace.Placement.Bottom,
                                    Func <bool> controlVisible      = null,
                                    double angleOffset = 0)
            : base(context)
        {
            this.getHeight      = getHeight;
            this.setHeight      = setHeight;
            this.getDiameters   = getDiameters;
            this.setDiameters   = setDiameters;
            this.controlVisible = controlVisible;
            this.placement      = placement;
            this.diameterIndex  = diameterIndex;
            this.angleOffset    = angleOffset;
            theme = MatterControl.AppContext.Theme;

            scaleController = new ScaleController(Object3DControlContext, null, null, null, null, getHeight, setHeight, getDiameters, setDiameters);

            diameterValueDisplayInfo = new InlineEditControl()
            {
                ForceHide        = ForceHideScale,
                GetDisplayString = (value) => "{0:0.0}".FormatWith(value),
            };

            diameterValueDisplayInfo.EditComplete += async(s, e) =>
            {
                var newDiameter = diameterValueDisplayInfo.Value != 0 ? diameterValueDisplayInfo.Value : getDiameters[diameterIndex]();

                if (newDiameter == scaleController.FinalState.Diameters[diameterIndex])
                {
                    return;
                }

                Vector3 lockedEdge = ObjectSpace.GetCenterPosition(ActiveSelectedItem, placement);
                scaleController.ScaleDiameter(newDiameter, diameterIndex);
                await ActiveSelectedItem.Rebuild();

                // and keep the locked edge in place
                Vector3 newLockedEdge = ObjectSpace.GetCenterPosition(ActiveSelectedItem, placement);
                ActiveSelectedItem.Translate(lockedEdge - newLockedEdge);

                scaleController.EditComplete();
                scaleController = new ScaleController(Object3DControlContext, null, null, null, null, getHeight, setHeight, getDiameters, setDiameters);
            };

            diameterValueDisplayInfo.VisibleChanged += (s, e) =>
            {
                if (!diameterValueDisplayInfo.Visible)
                {
                    hadClickOnControl = false;
                }
            };

            Object3DControlContext.GuiSurface.AddChild(diameterValueDisplayInfo);

            DrawOnTop = true;

            grabControlMesh = SphereObject3D.CreateSphere(grabControlSize, 15, 10);

            CollisionVolume = grabControlMesh.CreateBVHData();

            Object3DControlContext.GuiSurface.BeforeDraw += Object3DControl_BeforeDraw;
        }