<|start_filename|>KinectFingerTracking/LightBuzz.Vitruvius.FingerTracking/Hand.cs<|end_filename|>
﻿using Microsoft.Kinect;
using System.Collections.Generic;
using System.Linq;

namespace LightBuzz.Vitruvius.FingerTracking
{
    /// <summary>
    /// Represents a hand.
    /// </summary>
    public class Hand
    {
        /// <summary>
        /// The tracking ID of the body the current hand belongs to.
        /// </summary>
        public ulong TrackingId { get; protected set; }

        /// <summary>
        /// A list of the detected fingers.
        /// </summary>
        public IList<Finger> Fingers { get; protected set; }

        /// <summary>
        /// A list of the contour points in the 3D Camera space.
        /// </summary>
        public IList<CameraSpacePoint> ContourCamera { get; protected set; }

        /// <summary>
        /// A list of the contour points in the 2D Depth space.
        /// </summary>
        public IList<DepthSpacePoint> ContourDepth { get; protected set; }

        /// <summary>
        /// A list of the countour points in the 2D Color space.
        /// </summary>
        public IList<ColorSpacePoint> ContourColor { get; protected set; }

        internal Hand(ulong trackingID, HandState state, IList<DepthPointEx> contour, IList<DepthPointEx> fingers, CoordinateMapper coordinateMapper)
        {
            TrackingId = trackingID;

            if (state == HandState.Open)
            {
                Fingers = fingers.Select(f => new Finger(f, coordinateMapper)).ToList();
            }
            else
            {
                Fingers = new List<Finger>();
            }

            ushort[] depths = contour.Select(d => (ushort)d.Z).ToArray();

            ContourDepth = contour.Select(p => new DepthSpacePoint { X = p.X, Y = p.Y }).ToArray();

            ContourCamera = new CameraSpacePoint[ContourDepth.Count];
            coordinateMapper.MapDepthPointsToCameraSpace((DepthSpacePoint[])ContourDepth, depths, (CameraSpacePoint[])ContourCamera);

            ContourColor = new ColorSpacePoint[ContourDepth.Count];
            coordinateMapper.MapDepthPointsToColorSpace((DepthSpacePoint[])ContourDepth, depths, (ColorSpacePoint[])ContourColor);
        }
    }
}


<|start_filename|>KinectFingerTracking/LightBuzz.Vitruvius.FingerTracking/PointFilter.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;

namespace LightBuzz.Vitruvius.FingerTracking
{
    internal class PointFilter
    {
        private readonly float MINIMUM_DISTANCE = 18f;

        public IList<DepthPointEx> Filter(IList<DepthPointEx> points)
        {
            IList<DepthPointEx> result = new List<DepthPointEx>();

            if (points.Count > 0)
            {
                var point = new DepthPointEx(points.First());
                result.Add(point);

                foreach (var currentSourcePoint in points.Skip(1))
                {
                    if (!PointsAreClose(currentSourcePoint, point))
                    {
                        point = new DepthPointEx(currentSourcePoint);
                        result.Add(point);
                    }
                }

                if (result.Count > 1)
                {
                    CheckFirstAndLastPoint(result);
                }
            }

            return result;
        }

        private void CheckFirstAndLastPoint(IList<DepthPointEx> points)
        {
            if (PointsAreClose(points.Last(), points.First()))
            {
                points.RemoveAt(points.Count - 1);
            }
        }

        private bool PointsAreClose(DepthPointEx sourcePoint, DepthPointEx destPoint)
        {
            return DepthPointEx.Distance(sourcePoint, destPoint) < MINIMUM_DISTANCE;
        }
    }
}


<|start_filename|>KinectFingerTracking/LightBuzz.Vitruvius.FingerTracking/GrahamScan.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;

namespace LightBuzz.Vitruvius.FingerTracking
{
    internal class GrahamScan
    {
        private IList<DepthPointEx> _points;

        public IList<DepthPointEx> ConvexHull(IList<DepthPointEx> points)
        {
            if (points.Count <= 3)
            {
                return points;
            }

            _points = points;

            var pointsSortedByAngle = SortPoints();
            int index = 1;

            while (index + 1 < pointsSortedByAngle.Count)
            {
                var value = PointAngleComparer.Compare(pointsSortedByAngle[index - 1], pointsSortedByAngle[index + 1], pointsSortedByAngle[index]);
                if (value < 0)
                {
                    index++;
                }
                else
                {
                    pointsSortedByAngle.RemoveAt(index);
                    if (index > 1)
                    {
                        index--;
                    }
                }
            }

            pointsSortedByAngle.Add(pointsSortedByAngle.First());

            return pointsSortedByAngle;
        }

        private DepthPointEx GetMinimumPoint()
        {
            var minPoint = _points[0];

            for (int index = 1; index < _points.Count; index++)
            {
                minPoint = GetMinimumPoint(minPoint, _points[index]);
            }

            return minPoint;
        }

        private DepthPointEx GetMinimumPoint(DepthPointEx p1, DepthPointEx p2)
        {
            if (p1.Y < p2.Y)
            {
                return p1;
            }
            else if (p1.Y == p2.Y)
            {
                if (p1.X < p2.X)
                {
                    return p1;
                }
            }

            return p2;
        }

        private IList<DepthPointEx> SortPoints()
        {
            var p0 = GetMinimumPoint();

            var comparer = new PointAngleComparer(p0);

            var sortedPoints = new List<DepthPointEx>(_points);
            sortedPoints.Remove(p0);
            sortedPoints.Insert(0, p0);
            sortedPoints.Sort(1, sortedPoints.Count - 1, comparer);

            return sortedPoints;
        }
    }

    internal class PointAngleComparer : IComparer<DepthPointEx>
    {
        private DepthPointEx p0;

        public PointAngleComparer(DepthPointEx zeroPoint)
        {
            p0 = zeroPoint;
        }

        public int Compare(DepthPointEx p1, DepthPointEx p2)
        {
            if (p1.Equals(p2))
            {
                return 0;
            }

            float value = Compare(p0, p1, p2);

            if (value == 0)
            {
                return 0;
            }
            if (value < 0)
            {
                return 1;
            }
            return -1;
        }

        public static float Compare(DepthPointEx p0, DepthPointEx p1, DepthPointEx p2)
        {
            return (p1.X - p0.X) * (p2.Y - p0.Y) - (p2.X - p0.X) * (p1.Y - p0.Y);
        }
    }
}


<|start_filename|>KinectFingerTracking/KinectFingerTracking/MainWindow.xaml.cs<|end_filename|>
﻿using LightBuzz.Vitruvius.FingerTracking;
using Microsoft.Kinect;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace KinectFingerTracking
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private KinectSensor _sensor = null;
        private InfraredFrameReader _infraredReader = null;
        private DepthFrameReader _depthReader = null;
        private BodyFrameReader _bodyReader = null;
        private IList<Body> _bodies;
        private Body _body;

        // Create a new reference of a HandsController.
        private HandsController _handsController = null;

        /// <summary>
        /// The main window of the app.
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            _sensor = KinectSensor.GetDefault();

            if (_sensor != null)
            {
                _depthReader = _sensor.DepthFrameSource.OpenReader();
                _depthReader.FrameArrived += DepthReader_FrameArrived;

                _infraredReader = _sensor.InfraredFrameSource.OpenReader();
                _infraredReader.FrameArrived += InfraredReader_FrameArrived;

                _bodyReader = _sensor.BodyFrameSource.OpenReader();
                _bodyReader.FrameArrived += BodyReader_FrameArrived;
                _bodies = new Body[_sensor.BodyFrameSource.BodyCount];

                // Initialize the HandsController and subscribe to the HandsDetected event.
                _handsController = new HandsController();
                _handsController.HandsDetected += HandsController_HandsDetected;

                _sensor.Open();
            }
        }

        private void DepthReader_FrameArrived(object sender, DepthFrameArrivedEventArgs e)
        {
            canvas.Children.Clear();

            using (DepthFrame frame = e.FrameReference.AcquireFrame())
            {
                if (frame != null)
                {
                    // 2) Update the HandsController using the array (or pointer) of the depth depth data, and the tracked body.
                    using (KinectBuffer buffer = frame.LockImageBuffer())
                    {
                        _handsController.Update(buffer.UnderlyingBuffer, _body);
                    }
                }
            }
        }

        private void InfraredReader_FrameArrived(object sender, InfraredFrameArrivedEventArgs e)
        {
            using (var frame = e.FrameReference.AcquireFrame())
            {
                if (frame != null)
                {
                    camera.Source = frame.ToBitmap();
                }
            }
        }

        private void BodyReader_FrameArrived(object sender, BodyFrameArrivedEventArgs e)
        {
            using (var bodyFrame = e.FrameReference.AcquireFrame())
            {
                if (bodyFrame != null)
                {
                    bodyFrame.GetAndRefreshBodyData(_bodies);

                    _body = _bodies.Where(b => b.IsTracked).FirstOrDefault();
                }
            }
        }

        private void HandsController_HandsDetected(object sender, HandCollection e)
        {
            // Display the results!

            if (e.HandLeft != null)
            {
                // Draw contour.
                foreach (var point in e.HandLeft.ContourDepth)
                {
                    DrawEllipse(point, Brushes.Green, 2.0);
                }

                // Draw fingers.
                foreach (var finger in e.HandLeft.Fingers)
                {
                    DrawEllipse(finger.DepthPoint, Brushes.White, 4.0);
                }
            }

            if (e.HandRight != null)
            {
                // Draw contour.
                foreach (var point in e.HandRight.ContourDepth)
                {
                    DrawEllipse(point, Brushes.Blue, 2.0);
                }

                // Draw fingers.
                foreach (var finger in e.HandRight.Fingers)
                {
                    DrawEllipse(finger.DepthPoint, Brushes.White, 4.0);
                }
            }
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (_bodyReader != null)
            {
                _bodyReader.Dispose();
                _bodyReader = null;
            }

            if (_depthReader != null)
            {
                _depthReader.Dispose();
                _depthReader = null;
            }

            if (_infraredReader != null)
            {
                _infraredReader.Dispose();
                _infraredReader = null;
            }

            if (_sensor != null)
            {
                _sensor.Close();
                _sensor = null;
            }
        }

        private void DrawEllipse(DepthSpacePoint point, Brush brush, double radius)
        {
            Ellipse ellipse = new Ellipse
            {
                Width = radius,
                Height = radius,
                Fill = brush
            };

            canvas.Children.Add(ellipse);

            Canvas.SetLeft(ellipse, point.X - radius / 2.0);
            Canvas.SetTop(ellipse, point.Y - radius / 2.0);
        }
    }
}


<|start_filename|>KinectFingerTracking/LightBuzz.Vitruvius.FingerTracking/DepthPointEx.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;

namespace LightBuzz.Vitruvius.FingerTracking
{
    internal struct DepthPointEx
    {
        public float X;
        public float Y;
        public float Z;

        public static DepthPointEx Zero = new DepthPointEx(0, 0, 0);

        public DepthPointEx(DepthPointEx point) : this()
        {
            X = point.X;
            Y = point.Y;
            Z = point.Z;
        }

        public DepthPointEx(float x, float y, float z) : this()
        {
            X = x;
            Y = y;
            Z = z;
        }

        public static double Distance(DepthPointEx p1, DepthPointEx p2)
        {
            return Math.Sqrt(Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.Y - p2.Y, 2) + Math.Pow(p1.Z - p2.Z, 2));
        }

        public static double Distance(double x1, double y1, double x2, double y2)
        {
            return Math.Sqrt(Math.Pow(x1 - x2, 2) + Math.Pow(y1 - y2, 2));
        }

        public static DepthPointEx Center(DepthPointEx p1, DepthPointEx p2)
        {
            return new DepthPointEx((p1.X + p2.X) / 2, (p1.Y + p2.Y) / 2, (p1.Z + p2.Z) / 2);
        }

        public static DepthPointEx Center(IList<DepthPointEx> points)
        {
            var center = DepthPointEx.Zero;
            if (points.Count > 0)
            {
                for (int index = 0; index < points.Count; index++)
                {
                    var p = points[index];
                    center.X += p.X;
                    center.Y += p.Y;
                    center.Z += p.Z;
                }

                center.X /= points.Count;
                center.Y /= points.Count;
                center.Z /= points.Count;
            }
            return center;
        }

        public static DepthPointEx FindNearestPoint(DepthPointEx target, IEnumerable<DepthPointEx> points)
        {
            var pointList = points.ToList();
            return pointList[FindIndexOfNearestPoint(target, pointList)];
        }

        public static int FindIndexOfNearestPoint(DepthPointEx target, IList<DepthPointEx> points)
        {
            int index = 0;
            int resultIndex = -1;
            double minDist = double.MaxValue;
            foreach (DepthPointEx p in points)
            {
                var distance = Distance(p.X, p.Y, target.X, target.Y);
                if (distance < minDist)
                {
                    resultIndex = index;
                    minDist = distance;
                }
                index++;
            }
            return resultIndex;
        }

        public static double Angle(DepthPointEx center, DepthPointEx start, DepthPointEx end)
        {
            return Angle(center.X, center.Y, start.X, start.Y, end.X, end.Y);
        }

        public static double Angle(float centerX, float centerY, float startX, float startY, float endX, float endY)
        {
            Vector first = new Vector(startX - centerX, startY - centerY);
            Vector second = new Vector(endX - centerX, endY - centerY);

            return Vector.AngleBetween(first, second);
        }
    }
}


<|start_filename|>KinectFingerTracking/LightBuzz.Vitruvius.FingerTracking/Finger.cs<|end_filename|>
﻿using Microsoft.Kinect;

namespace LightBuzz.Vitruvius.FingerTracking
{
    /// <summary>
    /// Represents a finger tip.
    /// </summary>
    public class Finger
    {
        /// <summary>
        /// The position of the fingertip in the 3D Camera space.
        /// </summary>
        public CameraSpacePoint CameraPoint { get; set; }

        /// <summary>
        /// The position of the fingertip in the 2D Depth space.
        /// </summary>
        public DepthSpacePoint DepthPoint { get; set; }

        /// <summary>
        /// The position of the fingertip in the 2D Color space.
        /// </summary>
        public ColorSpacePoint ColorPoint { get; set; }

        internal Finger(DepthPointEx point, CoordinateMapper coordinateMapper)
        {
            ushort depth = (ushort)point.Z;

            DepthPoint = new DepthSpacePoint
            {
                X = point.X,
                Y = point.Y
            };

            ColorPoint = coordinateMapper.MapDepthPointToColorSpace(DepthPoint, (ushort)point.Z);

            CameraPoint = coordinateMapper.MapDepthPointToCameraSpace(DepthPoint, (ushort)point.Z);
        }
    }
}


<|start_filename|>KinectFingerTracking/KinectFingerTracking/Utilities/Bitmaps.cs<|end_filename|>
﻿using Microsoft.Kinect;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace KinectFingerTracking
{
    internal class InfraredBitmapGenerator
    {
        public int Width { get; protected set; }

        public int Height { get; protected set; }

        public ushort[] InfraredData { get; protected set; }

        public byte[] Pixels { get; protected set; }

        public WriteableBitmap Bitmap { get; protected set; }

        public void Update(InfraredFrame frame)
        {
            if (Bitmap == null)
            {
                Width = frame.FrameDescription.Width;
                Height = frame.FrameDescription.Height;
                InfraredData = new ushort[Width * Height];
                Pixels = new byte[Width * Height * 4];
                Bitmap = new WriteableBitmap(Width, Height, 96.0, 96.0, PixelFormats.Bgr32, null);
            }

            frame.CopyFrameDataToArray(InfraredData);
            
            int colorIndex = 0;

            for (int infraredIndex = 0; infraredIndex < InfraredData.Length; infraredIndex++)
            {
                ushort ir = InfraredData[infraredIndex];
                
                byte intensity = (byte)(ir >> 6);

                Pixels[colorIndex++] = intensity; // Blue
                Pixels[colorIndex++] = intensity; // Green   
                Pixels[colorIndex++] = intensity; // Red
                
                colorIndex++;
            }

            Bitmap.Lock();

            Marshal.Copy(Pixels, 0, Bitmap.BackBuffer, Pixels.Length);
            Bitmap.AddDirtyRect(new Int32Rect(0, 0, Width, Height));

            Bitmap.Unlock();
        }
    }


    internal static class BitmapExtensions
    {
        private static InfraredBitmapGenerator _bitmapGenerator = new InfraredBitmapGenerator();

        public static WriteableBitmap ToBitmap(this InfraredFrame frame)
        {
            _bitmapGenerator.Update(frame);

            return _bitmapGenerator.Bitmap;
        }
    }
}


<|start_filename|>KinectFingerTracking/LightBuzz.Vitruvius.FingerTracking/HandCollection.cs<|end_filename|>
﻿namespace LightBuzz.Vitruvius.FingerTracking
{
    /// <summary>
    /// A collection of hands for a specific Body.
    /// </summary>
    public class HandCollection
    {
        /// <summary>
        /// The tracking ID of the current body.
        /// </summary>
        public ulong TrackingId { get; set; }

        /// <summary>
        /// The left hand data of the current body.
        /// </summary>
        public Hand HandLeft { get; set; }

        /// <summary>
        /// The right hand data of the current body.
        /// </summary>
        public Hand HandRight { get; set; }
    }
}
