Manish Jain
Manish Jain

Reputation: 1353

How to draw a Field of View of Camera using Circle in WPF?

I am trying to paint an arc using WPF, but I somehow cannot figure out how to do this. I have searched a lot and came through Path property of WPF but i found that this is not good enough in my case. My requirement is to show the Field of View of CCTV camera on a wpf.

There are following inputs from the user: 1. Center point of circle. 2. Radius of Circle. 3. Angle(0-360)

I want to allow user to draw a region of a circle or a full circle and can change the region by changing the angle and radius. Note: 1. Center is a fixed location on the canvas. 2. User can change the figure(region) dynamically.

Suppose user will provide angle 45 degree then the Circle will look like as in below image: enter image description here

Upvotes: 1

Views: 1178

Answers (2)

Bradley Uffner
Bradley Uffner

Reputation: 16991

After spending some quality time with my old Algebra text-book, I've got a fully working solution for you.

This uses the WpfLib Nuget package (Disclaimer: I am the author of this package) for INotifyPropertyChanged support, but you should easily be able to whatever change event system you want. The full solution can be downloaded from BitBucket.

enter image description here

The XAML:

<Window
    x:Class="ViewingAngle.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:ViewingAngle"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="525"
    Height="350"
    d:DataContext="{d:DesignInstance local:MainWindowVm}"
    mc:Ignorable="d">
    <DockPanel Margin="4">
        <StackPanel DockPanel.Dock="Bottom">
            <TextBlock Margin="4,4,4,0" Text="{Binding Path=FieldOfView, StringFormat='Field of View: {0}'}" />
            <Slider
                Margin="4"
                Maximum="360"
                Minimum="0"
                TickFrequency="5"
                TickPlacement="Both"
                Value="{Binding Path=FieldOfView}" />
            <TextBlock Margin="4,4,4,0" Text="{Binding Path=TargetAngle, StringFormat='Target Angle: {0}'}" />
            <Slider
                Margin="4"
                Maximum="360"
                Minimum="0"
                TickFrequency="5"
                TickPlacement="Both"
                Value="{Binding Path=TargetAngle}" />
        </StackPanel>

        <Viewbox Margin="4" Stretch="Uniform">
            <Canvas Width="500" Height="500">
                <Path
                    Fill="Blue"
                    Stroke="Blue"
                    StrokeThickness="1">
                    <Path.Data>
                        <PathGeometry>
                            <PathGeometry.Figures>
                                <PathFigure StartPoint="250,250">
                                    <LineSegment Point="{Binding Path=StartPoint}" />
                                    <ArcSegment
                                        IsLargeArc="{Binding Path=IsLargeArc}"
                                        Point="{Binding Path=EndPoint}"
                                        Size="250,250"
                                        SweepDirection="Clockwise" />
                                    <LineSegment Point="250,250" />
                                </PathFigure>
                            </PathGeometry.Figures>
                        </PathGeometry>
                    </Path.Data>
                </Path>
            </Canvas>
        </Viewbox>
    </DockPanel>
</Window>

The View-Model:

using System;
using System.Windows;
using AgentOctal.WpfLib;

namespace ViewingAngle
{
    class MainWindowVm : ViewModel
    {
        private const int Radius = 250;
        private const int CenterX = 250;
        private const int CenterY = 250;


        public MainWindowVm()
        {
            FieldOfView = 45;
            TargetAngle = 90;
        }

        private int _fieldOfView;
        public int FieldOfView
        {
            get { return _fieldOfView; }
            set
            {
                SetValue(ref _fieldOfView, value);
                RecalculateArc();
            }
        }

        private int _targetAngle;
        public int TargetAngle
        {
            get { return _targetAngle; }
            set
            {
                SetValue(ref _targetAngle, value);
                RecalculateArc();
            }
        }

        private double GetRadians(int angle)
        {
            return angle * Math.PI / 180;
        }

        private void RecalculateArc()
        {
            var targetAngle = GetRadians(_targetAngle);
            var fieldOfView = GetRadians(_fieldOfView);
            var halfFieldOfView = fieldOfView / 2;

            var startAngle = targetAngle - halfFieldOfView;
            var endAngle = targetAngle + halfFieldOfView;
            double angleDiff = endAngle - startAngle;
            IsLargeArc = angleDiff >= Math.PI;

            StartPoint = new Point(CenterX + Radius * Math.Cos(startAngle), CenterY + Radius * Math.Sin(startAngle));
            EndPoint = new Point(CenterX + Radius * Math.Cos(endAngle), CenterY + Radius * Math.Sin(endAngle));
        }

        private Point _startPoint;
        public Point StartPoint
        {
            get { return _startPoint; }
            set { SetValue(ref _startPoint, value); }
        }

        private Point _endPoint;
        public Point EndPoint
        {
            get { return _endPoint; }
            set { SetValue(ref _endPoint, value); }
        }

        private bool _isLargeArc;
        public bool IsLargeArc
        {
            get { return _isLargeArc; }
            set { SetValue(ref _isLargeArc, value); }
        }
    }
}

All the magic happens in RecalculateArc which gets called whenever TargetAngle or FieldOfView are changed. All the math has to happen in radians, so the first thing it does is convert the values. It calculates new StartPoint and EndPoint values for the ArcSegment using some fairly simple algebra involving Sine and Cosine (though I still had to look the math up, because who remembers this stuff after high-school?).

In XAML, I placed some sliders bound to TargetAngle, and FieldOfView to allow you to control the angles. It also contains the Canvas where the graphics are drawn, this is inside a ViewBox, just to make the indicator fill the available space.

The Canvas contains a Path, that is made up of one PathFigure. The PathFigure starts at 250,250 (the center of the 500 x 500 Canvas), draws a LineSegment out to the arc's starting point, which is bound to StartPoint. An ArcSegment is then added, that ends at EndPoint. IsLargeArc is just used to let the drawing system know which "half" of the arc to draw. Another LineSegment that end back at the center is added to finish off the PathFigure.

Upvotes: 2

P.Manthe
P.Manthe

Reputation: 960

Here is a simple way to do it. Of course you'll need to put your own code to make it dynamic, but it is quite easy to control: in my example, I put two different figures so you will have two "field of view":

    <Ellipse Fill="Blue" Width="100" Height="100">
        <Ellipse.Clip>
            <PathGeometry>
                <PathGeometry.Figures>
                    <PathFigure StartPoint="50,50" IsClosed="True">
                        <LineSegment Point="100,50"/>
                        <LineSegment Point="100,0"/>
                    </PathFigure>
                    <PathFigure StartPoint="50,50" IsClosed="True">
                        <LineSegment Point="80,0"/>
                        <LineSegment Point="30,0"/>
                    </PathFigure>
                </PathGeometry.Figures>
            </PathGeometry>
        </Ellipse.Clip>
    </Ellipse>

Upvotes: 1

Related Questions