//***************************************************************************************/
// 
// File name: KinectCamera.cpp
// Location:  ...\Matrox Imaging\MILxxx\Examples\Processing\3dCamera\MicrosoftKinect\C++
//             
//
// Synopsis:  Implementation of the CKinectCamera class, a class that manages
//            the Microsoft Kinect.
//
// Copyright (C) Matrox Electronic Systems Ltd., 1992-2015.
// All Rights Reserved

#include <mil.h>
#include <Windows.h>
#include "KinectCamera.h"

#if USE_REAL_CAMERA
#if (KINECT_CAMERA_VERSION == 1)

// Include the header for the Kinect. Define the static_assert function for compiler < VS2010
#pragma comment(lib, "Kinect10.lib")
#if _MSC_VER < 1600
#define static_assert(a, b)
#endif
#include "NuiApi.h"

// Constants.
static const NUI_IMAGE_RESOLUTION KINECT_COLOR_IMAGE_RESOLUTION = NUI_IMAGE_RESOLUTION_640x480;
static const NUI_IMAGE_RESOLUTION KINECT_DEPTH_IMAGE_RESOLUTION = NUI_IMAGE_RESOLUTION_640x480;
static const MIL_INT KINECT_COLOR_NB_FRAMES = 2;
static const MIL_INT KINECT_DEPTH_NB_FRAMES = 2;
static const MIL_INT KINECT_IR_NB_FRAMES = 2;

//*****************************************************************************
// Constructor.
//*****************************************************************************
CKinectCamera::CKinectCamera(MIL_ID MilSystem)
   : CKinectCameraBase(MilSystem)
   {
   // Create the sensor.
   if(FAILED(NuiCreateSensorByIndex(0, &m_pNuiSensor)))
      m_SensorStatus = KINECT_CAMERA_NOT_OK;  
   }


//*****************************************************************************
// Destructor.
//*****************************************************************************
CKinectCamera::~CKinectCamera()
   {
   // Close down the camera.
   CloseDown();
   }

void CKinectCamera::ShutdownCamera()
   {
   // Shut down the sensor.
   if(m_pNuiSensor)
      m_pNuiSensor->NuiShutdown();

   // Release the sensor. This should also close the image streams handles.
   if(m_pNuiSensor)
      {
      m_pNuiSensor->Release();
      m_pNuiSensor = NULL;
      }
   }

//*****************************************************************************
// Initialization function.
//*****************************************************************************
MIL_INT CKinectCamera::InitCamera(ColorStreamTypeEnum ColorStreamType, bool UseDepth)
   {
   if(m_SensorStatus == KINECT_CAMERA_NOT_OK)
      return m_SensorStatus;

   // Set whether to use the IR camera.
   m_ColorStreamType = ColorStreamType;

   // Open/Close the IR Emitter. 
   if(FAILED(m_pNuiSensor->NuiSetForceInfraredEmitterOff(!UseDepth)))
      return KINECT_CAMERA_NOT_OK;  

   // ReInitialize the Kinect sensor.
   if(FAILED(m_pNuiSensor->NuiInitialize(NUI_INITIALIZE_FLAG_USES_COLOR + (UseDepth ? NUI_INITIALIZE_FLAG_USES_DEPTH : 0))))
      return KINECT_CAMERA_NOT_OK;

   // Open the color/infrared streams of the Kinect.
   if(FAILED(m_pNuiSensor->NuiImageStreamOpen(m_ColorStreamType == enColor? NUI_IMAGE_TYPE_COLOR : NUI_IMAGE_TYPE_COLOR_INFRARED,
                                              KINECT_COLOR_IMAGE_RESOLUTION,
                                              0,
                                              KINECT_COLOR_NB_FRAMES,
                                              m_NextColorFrameEvent,
                                              &m_ColorStreamHandle)))
      return KINECT_CAMERA_NOT_OK;

   // Open the depth stream(do not open if in IR mode).
   if(UseDepth)
      {
      if(FAILED(m_pNuiSensor->NuiImageStreamOpen(NUI_IMAGE_TYPE_DEPTH,
                                                 KINECT_DEPTH_IMAGE_RESOLUTION,
                                                 0,
                                                 KINECT_DEPTH_NB_FRAMES,
                                                 m_NextDepthFrameEvent,
                                                 &m_DepthStreamHandle)))
            return KINECT_CAMERA_NOT_OK;

      // Put the depth steam in near mode.
      if(FAILED(NuiImageStreamSetImageFrameFlags(m_DepthStreamHandle, NUI_IMAGE_STREAM_FLAG_ENABLE_NEAR_MODE)))
         return KINECT_CAMERA_NOT_OK;
      }   
   return KINECT_CAMERA_OK;
   }

//*****************************************************************************
// Function that processes the Kinect buffer.
//*****************************************************************************
void CKinectCamera::ProcessKinectBufferColor(MIL_DIG_HOOK_FUNCTION_PTR HookHandlerPtr, void* UserDataPtr)
   {
   if(m_ColorStreamType == enColor)
      ProcessKinectBuffer(m_ColorStreamHandle, HookHandlerPtr, UserDataPtr, 3, 8+M_UNSIGNED, M_BGR32 + M_PACKED);
   else
      ProcessKinectBuffer(m_ColorStreamHandle, HookHandlerPtr, UserDataPtr, 1, 16+M_UNSIGNED, M_NULL);   
      
   }

void CKinectCamera::ProcessKinectBufferDepth(MIL_DIG_HOOK_FUNCTION_PTR HookHandlerPtr, void* UserDataPtr)
   {
   ProcessKinectBuffer(m_DepthStreamHandle, HookHandlerPtr, UserDataPtr, 1, 16+M_UNSIGNED, M_NULL);
   }

void CKinectCamera::ProcessKinectBuffer(HANDLE NuiImageStream,
                                        MIL_DIG_HOOK_FUNCTION_PTR HookHandlerPtr,
                                        void* UserDataPtr,
                                        MIL_INT NbBands,
                                        MIL_INT Type,
                                        MIL_INT Attribute)
   {
   NUI_IMAGE_FRAME NuiImageFrame;
   NUI_LOCKED_RECT NuiLockedRect;
   NUI_SURFACE_DESC NuiSurfaceDesc;

   // Get the image frame.
   if(FAILED(m_pNuiSensor->NuiImageStreamGetNextFrame(NuiImageStream, 0,  &NuiImageFrame)))
      return;

   // Get the surface descriptor.
   NuiImageFrame.pFrameTexture->GetLevelDesc(0, &NuiSurfaceDesc);

   // Get the locked rect of the image frame.
   NuiImageFrame.pFrameTexture->LockRect(0, &NuiLockedRect, NULL, 0);   

   // Create a mil image on the Kinect data. 
   MIL_ID MilKinectImage = MbufCreateColor(GetMilSystem(),
                                           NbBands,
                                           NuiSurfaceDesc.Width,
                                           NuiSurfaceDesc.Height,
                                           Type,
                                           M_IMAGE + M_PROC + Attribute,
                                           M_HOST_ADDRESS + M_PITCH_BYTE,
                                           NuiLockedRect.Pitch,
                                           (void**)(&NuiLockedRect.pBits),
                                           M_NULL);

   // Call the Mil hook function. 
   (*HookHandlerPtr)(0, MilKinectImage, UserDataPtr);

   // Free the created buffer.
   MbufFree(MilKinectImage);

   // Release the locked rect of the image frame.
   NuiImageFrame.pFrameTexture->UnlockRect(0);

   // Release the image frame.
   m_pNuiSensor->NuiImageStreamReleaseFrame(NuiImageStream, &NuiImageFrame);
   }

#elif (KINECT_CAMERA_VERSION == 2)

// Visual studio 2012 and above is required to run the Kinect 2.0
#if _MSC_VER < 1700
   #error Visual Studio 2012 or higher is required to use the Kinect 2.0 SDK 
#endif

// Include the header for the Kinect.
#pragma comment(lib, "Kinect20.lib")
#include "Kinect.h"

// Constants.
static const MIL_INT KINECT_COLOR_NB_FRAMES = 2;
static const MIL_INT KINECT_DEPTH_NB_FRAMES = 2;
static const MIL_INT KINECT_IR_NB_FRAMES = 2;

//*****************************************************************************
// Constructor.
//*****************************************************************************
CKinectOneCamera::CKinectOneCamera(MIL_ID MilSystem)
   : CKinectCameraBase(MilSystem)
   {
   // Create the sensor.
   if (FAILED(GetDefaultKinectSensor(&m_pKinectSensor)))
      m_SensorStatus = KINECT_CAMERA_NOT_OK;
   }

//*****************************************************************************
// Destructor.
//*****************************************************************************
CKinectOneCamera::~CKinectOneCamera()
   {
   // Close down the camera.
   CloseDown();
   }

void CKinectOneCamera::ShutdownCamera()
   {
   // Shut down the sensor.
   if (m_pKinectSensor)
      m_pKinectSensor->Close();

   // Release the sensor. This should also close the image streams handles.
   if (m_pKinectSensor)
      {
      m_pKinectSensor->Release();
      m_pKinectSensor = NULL;
      }
   }

//*****************************************************************************
// Initialization function.
//*****************************************************************************
MIL_INT CKinectOneCamera::InitCamera(ColorStreamTypeEnum ColorStreamType, bool UseDepth)
   {
   if (m_SensorStatus == KINECT_CAMERA_NOT_OK)
      return m_SensorStatus;

   // Set whether to use the IR camera.
   m_ColorStreamType = ColorStreamType;
      
   // Open the color/infrared streams of the Kinect.
   if (ColorStreamType == enColor)
      {
      IColorFrameSource* pColorFrameSource = NULL;
      if (FAILED(m_pKinectSensor->get_ColorFrameSource(&pColorFrameSource)))
         return KINECT_CAMERA_NOT_OK;

      if (FAILED(pColorFrameSource->OpenReader(&m_ColorFrameReader)))
         return KINECT_CAMERA_NOT_OK;

      WAITABLE_HANDLE NextColorFrameWaitableEvent;
      if (FAILED(m_ColorFrameReader->SubscribeFrameArrived(&NextColorFrameWaitableEvent)))
         return KINECT_CAMERA_NOT_OK;
      m_NextColorFrameEvent = (HANDLE)NextColorFrameWaitableEvent;
      }
   else if (ColorStreamType == enIR)
      {
      IInfraredFrameSource* pInfraredFrameSource = NULL;
      if (FAILED(m_pKinectSensor->get_InfraredFrameSource(&pInfraredFrameSource)))
         return KINECT_CAMERA_NOT_OK;

      if (FAILED(pInfraredFrameSource->OpenReader(&m_IRFrameReader)))
         return KINECT_CAMERA_NOT_OK;

      WAITABLE_HANDLE NextColorFrameWaitableEvent;
      if (FAILED(m_IRFrameReader->SubscribeFrameArrived(&NextColorFrameWaitableEvent)))
         return KINECT_CAMERA_NOT_OK;
      m_NextColorFrameEvent = (HANDLE)NextColorFrameWaitableEvent;
      }

   // Open the depth stream
   if (UseDepth)
      {
      IDepthFrameSource* pDepthFrameSource = NULL;
      if (FAILED(m_pKinectSensor->get_DepthFrameSource(&pDepthFrameSource)))
         return KINECT_CAMERA_NOT_OK;

      if (FAILED(pDepthFrameSource->OpenReader(&m_DepthFrameReader)))
         return KINECT_CAMERA_NOT_OK;

      WAITABLE_HANDLE NextDepthFrameWaitableEvent;
      if (FAILED(m_DepthFrameReader->SubscribeFrameArrived(&NextDepthFrameWaitableEvent)))
         return KINECT_CAMERA_NOT_OK;
      m_NextDepthFrameEvent = (HANDLE)NextDepthFrameWaitableEvent;

      // Put the depth steam in near mode.
      }

   // Open the kinect sensor
   if (FAILED(m_pKinectSensor->Open()))
      return KINECT_CAMERA_NOT_OK;

   return KINECT_CAMERA_OK;
   }

//*****************************************************************************
// Function that processes the Kinect buffer.
//*****************************************************************************

// Explicit template instanciation
template void CKinectOneCamera::ProcessKinectBuffer<IColorFrameReader, IColorFrameArrivedEventArgs, IColorFrameReference, IColorFrame>             (IColorFrameReader* FrameReader, HANDLE NextFrameEvent, MIL_DIG_HOOK_FUNCTION_PTR HookHandlerPtr, void* UserDataPtr, MIL_INT NbBands, MIL_INT Type, MIL_INT Attribute);
template void CKinectOneCamera::ProcessKinectBuffer<IInfraredFrameReader, IInfraredFrameArrivedEventArgs, IInfraredFrameReference, IInfraredFrame> (IInfraredFrameReader* FrameReader, HANDLE NextFrameEvent, MIL_DIG_HOOK_FUNCTION_PTR HookHandlerPtr, void* UserDataPtr, MIL_INT NbBands, MIL_INT Type, MIL_INT Attribute);
template void CKinectOneCamera::ProcessKinectBuffer<IDepthFrameReader, IDepthFrameArrivedEventArgs, IDepthFrameReference, IDepthFrame>             (IDepthFrameReader* FrameReader, HANDLE NextFrameEvent, MIL_DIG_HOOK_FUNCTION_PTR HookHandlerPtr, void* UserDataPtr, MIL_INT NbBands, MIL_INT Type, MIL_INT Attribute);

template <>
void CKinectOneCamera::GetFrameData<IColorFrame>(IColorFrame* pFrame, void** ppFrameData)
   {
   UINT nBufferSize;   
   pFrame->AccessRawUnderlyingBuffer(&nBufferSize, reinterpret_cast<BYTE**>(ppFrameData));
   }

template <class IFrame>
void CKinectOneCamera::GetFrameData(IFrame* pFrame, void** ppFrameData)
   {
   UINT nBufferSize;
   pFrame->AccessUnderlyingBuffer(&nBufferSize, reinterpret_cast<UINT16**>(ppFrameData));
   }


void CKinectOneCamera::ProcessKinectBufferColor(MIL_DIG_HOOK_FUNCTION_PTR HookHandlerPtr, void* UserDataPtr)
   {
   if (m_ColorStreamType == enColor)
      ProcessKinectBuffer<IColorFrameReader, IColorFrameArrivedEventArgs, IColorFrameReference, IColorFrame>(m_ColorFrameReader, (HANDLE)m_NextColorFrameEvent, HookHandlerPtr, UserDataPtr, 3, 8 + M_UNSIGNED, M_YUV16 + M_PACKED);
   else
      ProcessKinectBuffer<IInfraredFrameReader, IInfraredFrameArrivedEventArgs, IInfraredFrameReference, IInfraredFrame>(m_IRFrameReader, (HANDLE)m_NextColorFrameEvent, HookHandlerPtr, UserDataPtr, 1, 16 + M_UNSIGNED, M_NULL);

   }

void CKinectOneCamera::ProcessKinectBufferDepth(MIL_DIG_HOOK_FUNCTION_PTR HookHandlerPtr, void* UserDataPtr)
   {
   ProcessKinectBuffer<IDepthFrameReader, IDepthFrameArrivedEventArgs, IDepthFrameReference, IDepthFrame>(m_DepthFrameReader, (HANDLE)m_NextDepthFrameEvent, HookHandlerPtr, UserDataPtr, 1, 16 + M_UNSIGNED, M_NULL);
   }

template <class IFrameReader, class IFrameArrivedEventArgs, class IFrameReference, class IFrame>
void CKinectOneCamera::ProcessKinectBuffer(IFrameReader* pFrameReader,
                                           HANDLE NextFrameEvent,
                                           MIL_DIG_HOOK_FUNCTION_PTR HookHandlerPtr,
                                           void* UserDataPtr,
                                           MIL_INT NbBands,
                                           MIL_INT Type,
                                           MIL_INT Attribute)
   {
   // Get the frame arguments.
   IFrameArrivedEventArgs* pFrameEventArgs = NULL;
   pFrameReader->GetFrameArrivedEventData((WAITABLE_HANDLE)NextFrameEvent, &pFrameEventArgs);

   // Get the frame reference.
   IFrameReference* pFrameRef = NULL;
   pFrameEventArgs->get_FrameReference(&pFrameRef);

   // Acquire the frame.
   IFrame* pFrame = NULL;
   HRESULT hr = pFrameRef->AcquireFrame(&pFrame);

   if (SUCCEEDED(hr))
      {
      // Get the frame description.
      IFrameDescription* pFrameDescription = NULL;
      pFrame->get_FrameDescription(&pFrameDescription);

      void* pFrameData = NULL;
      GetFrameData(pFrame, &pFrameData);   

      // Create a mil image on the Kinect data. 
      int Width;
      int Height;
      unsigned int BytesPerPixels;
      pFrameDescription->get_Width(&Width);
      pFrameDescription->get_Height(&Height);
      pFrameDescription->get_BytesPerPixel(&BytesPerPixels);
      MIL_ID MilKinectImage = MbufCreateColor(GetMilSystem(),
                                              NbBands,
                                              Width,
                                              Height,
                                              Type,
                                              M_IMAGE + M_PROC + Attribute,
                                              M_HOST_ADDRESS + M_PITCH_BYTE,
                                              BytesPerPixels * Width,
                                              (void**)(&pFrameData),
                                              M_NULL);

      // Call the Mil hook function. 
      (*HookHandlerPtr)(0, MilKinectImage, UserDataPtr);

      // Free the created buffer.
      MbufFree(MilKinectImage);

      // Release the frame
      pFrame->Release();
      }

   // Release the frame reference
   pFrameRef->Release();
   }

#endif // KINECT_CAMERA_VERSION
#endif // USE_REAL_CAMERA