public static Geometry CreateGeometry(GunPitchLimitsComponent traverse,
                                              TurretYawLimits yawLimits,
                                              double size,
                                              Point center,
                                              double geometryRotation = 0,
                                              double margin           = 0,
                                              double padding          = 0,
                                              Func <double, double> verticalTraverseTransform = null)
        {
            if (verticalTraverseTransform == null)
            {
                verticalTraverseTransform = GunTraverseHelper.DefaultVerticalTraverseTransform;
            }

            if (traverse.HasSingularValue && yawLimits.Range == 360)
            {
                var radius = size / 2 - margin;
                return(new EllipseGeometry(center, radius, radius));
            }
            else
            {
                var maxRadiusInDegrees = traverse.GetMaxValue();
                var scale = (size / 2 - margin - padding) / maxRadiusInDegrees;

                Func <double, double> radiusConverter = r => padding + Math.Max(verticalTraverseTransform(r) * scale, 0);

                return(GunTraverseHelper.CreateGeometry(traverse, yawLimits, center, radiusConverter, geometryRotation));
            }
        }
        public static void CartesianToPolar(Point center, Point point, out double radius, out double degree)
        {
            var d = point - center;

            radius = d.Length;
            degree = GunTraverseHelper.NormalizeAngle(Math.Atan2(d.Y, d.X) * 180 / Math.PI);
        }
        private static Geometry ProcessGeometryHorizontalTraverse(GunPitchLimitsComponent traverse,
                                                                  TurretYawLimits yawLimits,
                                                                  Point center,
                                                                  double geometryRotation,
                                                                  Geometry geometry,
                                                                  double maxRadius)
        {
            if (yawLimits == null || yawLimits.Range == 360)
            {
                return(geometry);
            }
            else
            {
                // create a horizontal fan
                var fanFigure = new PathFigure {
                    StartPoint = center
                };
                var point = GunTraverseHelper.PolarToCartesian(center, maxRadius, 360 - yawLimits.Right + geometryRotation);
                fanFigure.Segments.Add(new LineSegment(point, true));
                point = GunTraverseHelper.PolarToCartesian(center, maxRadius, -yawLimits.Left + geometryRotation);
                fanFigure.Segments.Add(new ArcSegment(point,
                                                      new Size(maxRadius, maxRadius),
                                                      yawLimits.Range,
                                                      yawLimits.Range > 180,
                                                      SweepDirection.Counterclockwise,
                                                      true));
                fanFigure.IsClosed = true;
                var fanGeometry = new PathGeometry();
                fanGeometry.Figures.Add(fanFigure);

                return(new CombinedGeometry(GeometryCombineMode.Intersect, geometry, fanGeometry));
            }
        }
        public static double RotateAngle(double from, double to, double delta, double limitRight, double limitLeft)
        {
            from = GunTraverseHelper.NormalizeAngle(from);
            to   = GunTraverseHelper.NormalizeAngle(to);

            if (Math.Abs(from - to) < 0.0001)
            {
                return(to);
            }

            delta = GunTraverseHelper.NormalizeAngle(delta);
            double direction;

            if (from < to)
            {
                if (limitRight + limitLeft < 360 &&
                    from <= limitRight &&
                    to >= 360 - limitLeft)
                {
                    direction = -1.0;
                }
                else if (to - from > 180)
                {
                    direction = -1.0;
                }
                else
                {
                    direction = 1.0;
                }
            }
            else
            {
                if (limitRight + limitLeft < 360 &&
                    to <= limitRight &&
                    from >= 360 - limitLeft)
                {
                    direction = 1.0;
                }
                else if (from - to > 180)
                {
                    direction = 1.0;
                }
                else
                {
                    direction = -1.0;
                }
            }

            var result = from + delta * direction;

            if ((from < to && result > to) || (from > to && result < to))
            {
                result = to;
            }

            return(result);
        }
        private Point CalculateGunPointerPos(Point center, double pitch, double yaw)
        {
            if (_elevationRadiusConverter == null)
            {
                return(new Point());
            }

            var distance = this.GunPitch < 0 ? _elevationRadiusConverter(pitch) : _depressionRadiusConverter(pitch);

            return(GunTraverseHelper.PolarToCartesian(center, distance, -yaw - this.VehicleYaw));
        }
        private void UpdateTraverse()
        {
            if (UseRealTraverseMode)
            {
                var now       = DateTime.Now;
                var deltaTime = now - _previousUpdateTime;
                var turretYaw = GunTraverseHelper.RotateAngle(
                    this.TurretYaw,
                    _destinationTurretYaw,
                    deltaTime.TotalSeconds * this.TurretTraverseSpeed,
                    this.TurretYawLimits == null ? 180.0 : this.TurretYawLimits.Right,
                    this.TurretYawLimits == null ? 180.0 : this.TurretYawLimits.Left);

                double elevation, depression;
                if (this.ElevationLimits == null)
                {
                    elevation  = 0;
                    depression = 0;
                }
                else
                {
                    elevation  = this.ElevationLimits.GetValue(turretYaw);
                    depression = this.DepressionLimits.GetValue(turretYaw);
                }

                var gunPitch = GunTraverseHelper.RotateAngle(
                    this.GunPitch + 180,
                    _destinationGunPitch + 180,
                    deltaTime.TotalSeconds * this.GunTraverseSpeed,
                    elevation,
                    depression) - 180;

                this.ClampRotation(ref turretYaw, ref gunPitch);

                _internalSettingTraverse = true;
                this.TurretYaw           = turretYaw;
                this.GunPitch            = gunPitch;
                _internalSettingTraverse = false;

                _previousUpdateTime = now;
            }
            else
            {
                _internalSettingTraverse = true;
                this.GunPitch            = _destinationGunPitch;
                this.TurretYaw           = _destinationTurretYaw;
                _internalSettingTraverse = false;
            }
        }
        private static PathSegment CreateTraverseFigureTransition(ref PathFigure figure, ref Point center, double startAngle, double transitionAngle, double from, double to)
        {
            const double step     = 0.1;
            var          endAngle = startAngle + transitionAngle;
            var          delta    = to - from;
            var          segment  = new PolyLineSegment();

            segment.IsStroked = true;
            for (var angle = startAngle + step; angle <= endAngle; angle += step)
            {
                var traverse = from + delta * (angle - startAngle) / transitionAngle;
                segment.Points.Add(GunTraverseHelper.PolarToCartesian(center, traverse, angle));
            }

            return(segment);
        }
        private void UpdateRotation()
        {
            var    position = Mouse.GetPosition(this.Root);
            double size;
            double radius;
            Point  center;

            this.GetDimentions(out size, out radius, out center);
            double distance, degree;

            GunTraverseHelper.CartesianToPolar(center, position, out distance, out degree);
            degree -= this.VehicleYaw;
            var turretYaw = GunTraverseHelper.NormalizeAngle(degree, -180);
            var gunPitch  = _inverseVerticalTraverseConverter(distance);

            this.SetRotation(turretYaw, gunPitch);
        }
        public void SetTraverse(GunPitchLimitsComponent gunPitch, TurretYawLimits turretYaw)
        {
            this.CurveCanvas.Children.Clear();

            if (gunPitch == null)
            {
                return;
            }

            var traverseFigureStrokeStyle = this.FindResource("TraverseFigure") as Style;

            var maxRadius = gunPitch.GetMaxValue();
            //maxRadius = Math.Ceiling(maxRadius / 5) * 5;
            const double margin = 1;

            var scale      = 80 / (maxRadius + margin);
            var size       = scale * maxRadius * 2;
            var center     = new Point(100, 100);
            var figurePath = new Path
            {
                Data = GunTraverseHelper.CreateGeometry(gunPitch,
                                                        turretYaw,
                                                        size,
                                                        center,
                                                        90,
                                                        scale * margin,
                                                        verticalTraverseTransform: v => v + 1),
                Style = traverseFigureStrokeStyle
            };

            this.CurveCanvas.Children.Add(figurePath);

            // draw references
            var thinReferenceGeometry  = new GeometryGroup();
            var thickReferenceGeometry = new GeometryGroup();

            const double referenceCircleGap     = 20;
            const double halfReferenceCircleGap = referenceCircleGap / 2;

            var sign         = Math.Sign(maxRadius);
            var absMaxRadius = Math.Abs(maxRadius);

            var thickDivisor = absMaxRadius > 45 ? 20 : absMaxRadius > 25 ? 10 : 5;
            var thinDivisor  = absMaxRadius > 25 ? 5 : 1;

            for (var i = thinDivisor; i <= absMaxRadius; i += thinDivisor)
            {
                var radius = scale * i * sign;

                Geometry geometry;

                var isThickRing = i % thickDivisor == 0;
                if (absMaxRadius < 10 || isThickRing)
                {
                    var gapHalfAngle = Math.Asin(halfReferenceCircleGap / radius) * 180 / Math.PI;
                    if (double.IsNaN(gapHalfAngle))
                    {
                        gapHalfAngle = 180;
                    }
                    var startPoint = GunTraverseHelper.PolarToCartesian(center, radius, gapHalfAngle);
                    var endPoint   = GunTraverseHelper.PolarToCartesian(center, radius, 360 - gapHalfAngle);
                    var arc        = new ArcSegment(endPoint, new Size(radius, radius), 360 - gapHalfAngle * 2, true, SweepDirection.Counterclockwise, true);
                    var figure     = new PathFigure(startPoint, new[] { arc }, false);
                    geometry = new PathGeometry(new[] { figure });


                    var referenceTextContainer = new Grid
                    {
                        Width  = 20,
                        Height = referenceCircleGap
                    };
                    var referenceTextContainerPosition = GunTraverseHelper.PolarToCartesian(center, radius, 0);
                    Canvas.SetLeft(referenceTextContainer, referenceTextContainerPosition.X - 10);
                    Canvas.SetTop(referenceTextContainer, referenceTextContainerPosition.Y - halfReferenceCircleGap);

                    var referenceText = new TextBlock
                    {
                        Text = (i * sign).ToString(),
                        HorizontalAlignment = HorizontalAlignment.Center,
                        VerticalAlignment   = VerticalAlignment.Center,
                        FontSize            = isThickRing ? 10 : 8,
                        Foreground          = isThickRing ? Brushes.White : new SolidColorBrush(Color.FromArgb(0x80, 0xff, 0xff, 0xff))
                    };


                    referenceTextContainer.Children.Add(referenceText);

                    this.CurveCanvas.Children.Add(referenceTextContainer);
                }
                else
                {
                    geometry = new EllipseGeometry(center, radius, radius);
                }

                if (isThickRing)
                {
                    thickReferenceGeometry.Children.Add(geometry);
                }
                else
                {
                    thinReferenceGeometry.Children.Add(geometry);
                }
            }

            var thinReferencePath = new Path
            {
                Data  = thinReferenceGeometry,
                Style = this.FindResource("ThinReferenceStroke") as Style
            };

            this.CurveCanvas.Children.Add(thinReferencePath);

            var thickReferencePath = new Path
            {
                Data  = thickReferenceGeometry,
                Style = this.FindResource("ThickReferenceStroke") as Style
            };

            this.CurveCanvas.Children.Add(thickReferencePath);
        }
        public static Geometry CreateGeometry(GunPitchLimitsComponent traverse,
                                              TurretYawLimits yawLimits,
                                              Point center,
                                              Func <double, double> radiusConverter,
                                              double geometryRotation = 0)
        {
            Geometry geometry;
            double   maxRadius;
            var      data = traverse.Data;

            if (traverse.HasSingularValue)
            {
                var radius = radiusConverter(data[0].Limit);
                geometry  = new EllipseGeometry(center, radius, radius);
                maxRadius = radius;
            }
            else
            {
                maxRadius = radiusConverter(traverse.GetMaxValue());

                var figureFigure = new PathFigure();

                var previousTheta  = 0.0;
                var previousRadius = 0.0;
                var previousNode   = (GunPitchLimitsComponent.PitchData)null;

                for (var i = 0; i < data.Length; ++i)
                {
                    var node       = data[i];
                    var theta      = geometryRotation + node.Angle * 360;
                    var radius     = radiusConverter(node.Limit);
                    var point      = GunTraverseHelper.PolarToCartesian(center, radius, theta);
                    var thetaRange = theta - previousTheta;

                    if (i == 0)
                    {
                        figureFigure.StartPoint = point;
                    }
                    else
                    {
                        Debug.Assert(previousNode != null, "previousNode != null");

                        if (previousNode.Angle == node.Angle)
                        {
                            figureFigure.Segments.Add(new LineSegment(point, true));
                        }
                        else if (previousNode.Limit == node.Limit)
                        {
                            figureFigure.Segments.Add(new ArcSegment(point,
                                                                     new Size(radius, radius),
                                                                     thetaRange,
                                                                     thetaRange >= 180,
                                                                     SweepDirection.Counterclockwise,
                                                                     true));
                        }
                        else
                        {
                            figureFigure.Segments.Add(GunTraverseHelper.CreateTraverseFigureTransition(ref figureFigure,
                                                                                                       ref center,
                                                                                                       previousTheta,
                                                                                                       thetaRange,
                                                                                                       previousRadius,
                                                                                                       radius));
                        }
                    }

                    previousTheta  = theta;
                    previousNode   = node;
                    previousRadius = radius;
                }


                var pathGeometry = new PathGeometry();
                pathGeometry.Figures.Add(figureFigure);
                geometry = pathGeometry;
            }

            return(GunTraverseHelper.ProcessGeometryHorizontalTraverse(traverse,
                                                                       yawLimits,
                                                                       center,
                                                                       geometryRotation,
                                                                       geometry,
                                                                       maxRadius));
        }
        private void UpdateTraverseBoundary()
        {
            if (this.ElevationLimits == null)
            {
                this.BoundaryCanvas.Children.Clear();
                return;
            }

            double size;
            double radius;
            Point  center;

            this.GetDimentions(out size, out radius, out center);
            if (size == 0)
            {
                return;
            }

            var oneThirdsRadius = radius / 3;

            var maxElevation  = this.ElevationLimits.GetMaxValue();
            var maxDepression = this.DepressionLimits.GetMaxValue();
            var maxTraverse   = -maxElevation + maxDepression;

            // elevation/outer figure

            _elevationRadiusConverter         = r => (-r + maxDepression) / maxTraverse * oneThirdsRadius * 2 + oneThirdsRadius;
            _depressionRadiusConverter        = r => (maxDepression - r) / maxTraverse * oneThirdsRadius * 2 + oneThirdsRadius;
            _inverseVerticalTraverseConverter = r => - ((r - oneThirdsRadius) / (oneThirdsRadius * 2) * maxTraverse - maxDepression);

            var elevationGeometry  = GunTraverseHelper.CreateGeometry(this.ElevationLimits, this.TurretYawLimits, center, _elevationRadiusConverter);
            var depressionGeometry = GunTraverseHelper.CreateGeometry(this.DepressionLimits, this.TurretYawLimits, center, _depressionRadiusConverter);

            var combinedGeometry = new CombinedGeometry(GeometryCombineMode.Exclude, elevationGeometry, depressionGeometry);

            var figure = new Path {
                Data = combinedGeometry
            };

            var borderStyle    = this.FindResource("Border") as Style;
            var delimiterStyle = this.FindResource("Delimiter") as Style;

            figure.Style = borderStyle;

            var delimiterCircle     = new Ellipse();
            var delimiterCircleSize = _depressionRadiusConverter(0) * 2;

            delimiterCircle.Width  = delimiterCircleSize;
            delimiterCircle.Height = delimiterCircleSize;
            delimiterCircle.Style  = delimiterStyle;

            Canvas.SetLeft(delimiterCircle, (size - delimiterCircleSize) / 2);
            Canvas.SetTop(delimiterCircle, (size - delimiterCircleSize) / 2);

            this.BoundaryCanvas.Children.Clear();

            this.BoundaryCanvas.Children.Add(delimiterCircle);
            this.BoundaryCanvas.Children.Add(figure);

            Canvas.SetLeft(this.YawDirectionLine, center.X);
            Canvas.SetTop(this.YawDirectionLine, center.Y);
        }