/*======================================================================================

	Program:		Range Imaging Toolkit (RITK)
	Module:			ritkOpenGLVolumeRayCastEntity.txx
	Version:		$Revision: 2908 $
	Date:			$Date: 2012-04-03 22:28:42 +0200 (Di, 03 Apr 2012) $

	Copyright (c) 2010-2011
	Pattern Recognition Lab, 
    University Erlangen-Nuremberg, Germany
    All rights reserved.

	This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
	See RITKCopyright.txt for more information.

	http://www5.informatik.uni-erlangen.de/research/software/ritk

========================================================================================*/


#include <QDataStream>
#include <QVariant>
#include <QDockWidget>
#include <QApplication>
#include <QComboBox>

#include "ritkOpenGLCamera.h"
#include "ritkOpenGLVolumeRayCastEntity.h"
#include "ritkDebugManager.h"
#include "ritkOpenGLVisualizationWidget.h"


#include <vnl/vnl_double_4x4.h>

// Degree to radian conversion
#define DEG2RAD(x) (3.14159265*x)/180.0

// Convenience macro to check for OpenGL errors
#define CHECK_GL_ERROR()												\
{																		\
	GLenum errCode = glGetError();										\
	if ( errCode != GL_NO_ERROR )										\
	{																	\
	std::cout << "An OpenGL error occured: " << errCode << std::endl;	\
	std::cout << gluErrorString(errCode) << std::endl;					\
	std::cout << __FILE__ << " Line " << __LINE__  << std::endl;		\
	}																	\
}


#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")



namespace ritk
{

//----------------------------------------------------------------------------
template<class TVolume, class TProjectionImage>
OpenGLVolumeRayCastEntity<TVolume,TProjectionImage>
::OpenGLVolumeRayCastEntity()
{
	// No raycaster by default
	m_RayCaster = NULL;

	// No volume by default
	m_Volume = NULL;

	// By default, no rendering of the bounding box
	m_RenderBoundingBox = false;
}


//----------------------------------------------------------------------------
template<class TVolume, class TProjectionImage>
OpenGLVolumeRayCastEntity<TVolume,TProjectionImage>
::~OpenGLVolumeRayCastEntity()
{
	// Clean up
	CloseGL();
}


//----------------------------------------------------------------------------
template<class TVolume, class TProjectionImage>
void
OpenGLVolumeRayCastEntity<TVolume,TProjectionImage>
::SetVolume(const VolumeType *Volume)
{
	{
		QMutexLocker locker(&m_Mutex);
		m_Volume = Volume;
	}

	emit StatusChanged();
}


//----------------------------------------------------------------------------
template<class TVolume, class TProjectionImage>
typename OpenGLVolumeRayCastEntity<TVolume,TProjectionImage>::BoundingBoxType
OpenGLVolumeRayCastEntity<TVolume,TProjectionImage>
::GetBoundingBox() const
{

	OpenGLVolumeRayCastEntity::BoundingBoxType BB;
	BB.Fill(0);

	if ( !m_Volume )
		return BB;

	for ( unsigned int dim = 0; dim < 3; dim++ )
	{
		// Set min
		BB[dim] = m_Volume->GetOrigin()[dim];
		// Set max
		BB[dim+3] = m_Volume->GetOrigin()[dim] + m_Volume->GetSpacing()[dim]*m_Volume->GetRequestedRegion().GetSize()[dim];
	}
	return BB;
}


//----------------------------------------------------------------------------
template<class TVolume, class TProjectionImage>
void
OpenGLVolumeRayCastEntity<TVolume,TProjectionImage>
::SetRenderBoundingBox(bool b)
{
	{
		QMutexLocker locker(&m_Mutex);
		m_RenderBoundingBox = b;
	}
}


//----------------------------------------------------------------------------
template<class TVolume, class TProjectionImage>
void
OpenGLVolumeRayCastEntity<TVolume,TProjectionImage>
::InitGL()
{
}


//----------------------------------------------------------------------------
template<class TVolume, class TProjectionImage>
void
OpenGLVolumeRayCastEntity<TVolume,TProjectionImage>
::CloseGL()
{
}


//----------------------------------------------------------------------------
template<class TVolume, class TProjectionImage>
void
OpenGLVolumeRayCastEntity<TVolume,TProjectionImage>
::Render2D(const ritk::OpenGLVisualizationWidget *Widget, QVector2D ImgDimensions)
{
	LOG_DEB("Render2D not supported...");
}


//----------------------------------------------------------------------------
template<class TVolume, class TProjectionImage>
void
OpenGLVolumeRayCastEntity<TVolume,TProjectionImage>
::Render3D(const ritk::OpenGLVisualizationWidget *Widget)
{
	if ( !m_Volume )
		return;

	// Should return immediately once we're initialized
	InitGL();

	// Lock ourself
	QMutexLocker locker(&m_Mutex);

	// Compute the viewing frustum from the camera
	ritk::OpenGLCamera::ConstPointer Camera = Widget->GetCamera();
	if ( !Camera->IsInitialized() )
		return;
	float FocalLength = Camera->GetClippingPlanes()[0];
	float FOV = Camera->GetFieldOfView();
	float AspectRatio = Camera->GetViewPort()[0]/Camera->GetViewPort()[1];
	float NearHeight = 2.f*tan(DEG2RAD(FOV)/2.f)*FocalLength;
	float NearWidth = NearHeight*AspectRatio;
	typename OutputImageType::SizeType OutputSize;
	OutputSize[0] = 512;
	OutputSize[1] = OutputSize[0]/AspectRatio;
	float sx = OutputSize[0] / NearWidth;
	float sy = OutputSize[1]/ NearHeight;

	// Set up the projection matrix
	typename RayCasterType::ProjectionMatrixType P;
	P[0][0] = FocalLength*sx;			P[0][1] = 0;						P[0][2] = -1*(NearWidth/2.0)*sx;		P[0][3] = 0;
	P[1][0] = 0;						P[1][1] = FocalLength*sy;			P[1][2] = -1*(NearHeight/2.0)*sy;		P[1][3] = 0;
	P[2][0] = 0;						P[2][1] = 0;						P[2][2] = 1;						P[2][3] = 0;
	/*std::cout << "ProjectionMatrix" << std::endl;
	std::cout << "================" << std::endl;
	std::cout << P << std::endl << std::endl;*/

	// Get the OpenGL model view matrix and transpose it
	float M[16];
	glGetFloatv(GL_MODELVIEW_MATRIX, M);
	vnl_double_4x4 ModelView;
	ModelView.fill(0);
	ModelView[0][0] = M[0];		ModelView[0][1] = M[4];		ModelView[0][2] = M[8];		ModelView[0][3] = M[12];
	ModelView[1][0] = M[1];		ModelView[1][1] = M[5];		ModelView[1][2] = M[9];		ModelView[1][3] = M[13];
	ModelView[2][0] = M[2];		ModelView[2][1] = M[6];		ModelView[2][2] = M[10];	ModelView[2][3] = M[14];
	ModelView[3][0] = M[3];		ModelView[3][1] = M[7];		ModelView[3][2] = M[11];	ModelView[3][3] = M[15];
	/*std::cout << "ModelviewMatrix" << std::endl;
	std::cout << "===============" << std::endl;
	std::cout << ModelView << std::endl << std::endl;*/

	// Flip the projector plane
	vnl_double_3x3 FlipMatrix;
	FlipMatrix.fill(0);
	FlipMatrix.fill_diagonal(1.0);
	FlipMatrix(0,0) = -1;
	FlipMatrix(1,1) = -1;
	/*std::cout << "FlipMatrix" << std::endl;
	std::cout << "===========" << std::endl;
	std::cout << FlipMatrix << std::endl << std::endl;*/

	// Concatenate model view, projection matrix and flip matrix
	typename RayCasterType::ProjectionMatrixType PFinal;
	if ( !ModelView.is_zero() )
	{
		PFinal = FlipMatrix*P*ModelView;
	}
	/*std::cout << "FinalProjectionMatrix" << std::endl;
	std::cout << "=====================" << std::endl;
	std::cout << PFinal << std::endl << std::endl;*/


	// Update the projector
	m_RayCaster->SetInput(m_Volume);
	m_RayCaster->SetProjectionMatrix(PFinal);
	m_RayCaster->SetOutputSize(OutputSize);
	try
	{
		m_RayCaster->Update();
	}
	catch ( itk::ExceptionObject &e )
	{
		std::cerr << e << std::endl;
	}

	// The actual rendering that has to be implemented by subclasses
	PostProcessAndRender();

	// Draw the bounding box (if requested)
	if ( m_RenderBoundingBox )
	{
		// Compute the bounding box' vertives
		float V000[3];
			V000[0] = m_Volume->GetOrigin()[0]; 
			V000[1] = m_Volume->GetOrigin()[1];
			V000[2] = m_Volume->GetOrigin()[2];
		float V001[3];
			V001[0] = m_Volume->GetOrigin()[0];
			V001[1] = m_Volume->GetOrigin()[1];
			V001[2] = m_Volume->GetOrigin()[2]+m_Volume->GetSpacing()[2]*m_Volume->GetRequestedRegion().GetSize()[2];
		float V010[3];
			V010[0] = m_Volume->GetOrigin()[0];
			V010[1] = m_Volume->GetOrigin()[1]+m_Volume->GetSpacing()[1]*m_Volume->GetRequestedRegion().GetSize()[1];
			V010[2] = m_Volume->GetOrigin()[2];
		float V011[3];
			V011[0] = m_Volume->GetOrigin()[0];
			V011[1] = m_Volume->GetOrigin()[1]+m_Volume->GetSpacing()[1]*m_Volume->GetRequestedRegion().GetSize()[1];
			V011[2] = m_Volume->GetOrigin()[2]+m_Volume->GetSpacing()[2]*m_Volume->GetRequestedRegion().GetSize()[2];
		float V100[3];
			V100[0] = m_Volume->GetOrigin()[0]+m_Volume->GetSpacing()[0]*m_Volume->GetRequestedRegion().GetSize()[0];
			V100[1] = m_Volume->GetOrigin()[1];
			V100[2] = m_Volume->GetOrigin()[2];
		float V101[3];
			V101[0] = m_Volume->GetOrigin()[0]+m_Volume->GetSpacing()[0]*m_Volume->GetRequestedRegion().GetSize()[0];
			V101[1] = m_Volume->GetOrigin()[1];
			V101[2] = m_Volume->GetOrigin()[2]+m_Volume->GetSpacing()[2]*m_Volume->GetRequestedRegion().GetSize()[2];
		float V110[3];
			V110[0] = m_Volume->GetOrigin()[0]+m_Volume->GetSpacing()[0]*m_Volume->GetRequestedRegion().GetSize()[0];
			V110[1] = m_Volume->GetOrigin()[1]+m_Volume->GetSpacing()[1]*m_Volume->GetRequestedRegion().GetSize()[1];
			V110[2] = m_Volume->GetOrigin()[2];
		float V111[3];
			V111[0] = m_Volume->GetOrigin()[0]+m_Volume->GetSpacing()[0]*m_Volume->GetRequestedRegion().GetSize()[0];
			V111[1] = m_Volume->GetOrigin()[1]+m_Volume->GetSpacing()[1]*m_Volume->GetRequestedRegion().GetSize()[1];
			V111[2] = m_Volume->GetOrigin()[2]+m_Volume->GetSpacing()[2]*m_Volume->GetRequestedRegion().GetSize()[2];


		// Disabe lighting
		glDisable(GL_LIGHTING);

		// Draw the BB vertices
		glPointSize(10);
		glColor3f(0,0,0);
		glBegin(GL_POINTS);
			// 000
			glVertex3f(V000[0], V000[1], V000[2]);
			// 001
			glVertex3f(V001[0], V001[1], V001[2]);
			// 010
			glVertex3f(V010[0], V010[1], V010[2]);
			// 011
			glVertex3f(V011[0], V011[1], V011[2]);
			// 100
			glVertex3f(V100[0], V100[1], V100[2]);
			// 101
			glVertex3f(V101[0], V101[1], V101[2]);
			// 110
			glVertex3f(V110[0], V110[1], V110[2]);
			// 111
			glVertex3f(V111[0], V111[1], V111[2]);
		glEnd();

		// First face
		glBegin(GL_LINE_STRIP);
			glColor3f(1,0,1);
			glVertex3f(V000[0], V000[1], V000[2]);
			glVertex3f(V001[0], V001[1], V001[2]);
			glVertex3f(V011[0], V011[1], V011[2]);
			glVertex3f(V010[0], V010[1], V010[2]);
			glVertex3f(V000[0], V000[1], V000[2]);
		glEnd();

		// Second face
		glBegin(GL_LINE_STRIP);
			glColor3f(1,1,0);
			glVertex3f(V100[0], V100[1], V100[2]);
			glVertex3f(V101[0], V101[1], V101[2]);
			glVertex3f(V111[0], V111[1], V111[2]);
			glVertex3f(V110[0], V110[1], V110[2]);
			glVertex3f(V100[0], V100[1], V100[2]);
		glEnd();

		// Restore lighting state
		glEnable(GL_LIGHTING);
	}
}


//----------------------------------------------------------------------------
template<class TVolume, class TProjectionImage>
QDockWidget*
OpenGLVolumeRayCastEntity<TVolume,TProjectionImage>
::GetControlUnit() const
{
	return NULL;
}


//----------------------------------------------------------------------------
template<class TVolume, class TProjectionImage>
void
OpenGLVolumeRayCastEntity<TVolume,TProjectionImage>
::PrintSelf(std::ostream& os, itk::Indent indent) const
{
	os << "OpenGLVolumeRayCastEntity" << std::endl;	
}


} // ritk
