//Author: Yusuf Sahillioglu
//Aim: My code of our paper: Sketch-based Articulated 3D Shape Retrieval, CG&A

//#define HAVE_SINGLEPRECISION_MATH //Eigen package does #include <complex> which conflicts with Inventor/C/basic.h definitions; so prevent them with this in files using include Inventor
#define _SECURE_SCL 0 //applications build on top of Open Inventor in release mode MUST define the _SECURE_SCL=0 preprocessor variable in order to avoid alignment problems. Using this mode reduce greatly memory footprint of STL objects and increase performance by a factor 2 to 10 depending on operations

//#define SOWIN_DLL #define COIN_DLL //doing here not sufficient 'cos there're inventor include's in other files as well; so put these preprocessor definitions to Projects->C/C++->Preprocessor

//viewer
#include <Inventor/Win/SoWin.h>
#include <Inventor/Win/viewers/SoWinExaminerViewer.h>
#include <Inventor/nodes/SoSwitch.h> //toggle visibility of a scene object
//event handling
#include <Inventor/nodes/SoEventCallback.h> 
#include <Inventor/events/SoKeyboardEvent.h>
/*#include <Inventor/events/SoMouseButtonEvent.h>
#include <Inventor/events/SoLocation2Event.h>*/

#include "Painter.h" //objects to screen


//#include <windows.h> //for get_time just-below [not here 'cos Box.h calls this already]
inline double get_time()
{
	LARGE_INTEGER t, frequency;
	QueryPerformanceCounter(&t);
	QueryPerformanceFrequency(&frequency);
	return (double)t.QuadPart/(double)frequency.QuadPart;
}
struct MyData
{
	SoSwitch* sketchSwitch, * strokesSwitch, * sketchSampleSwitch;
	SoWinExaminerViewer* myViewer;
	SoSeparator* root;
	Painter* painter;
};
void keyHit(void* userData, SoEventCallback* eventCB)
{
	MyData* md = (MyData*) userData;
	const SoEvent* theEvent = eventCB->getEvent();

	if (SO_KEY_PRESS_EVENT(theEvent, M))
		md->sketchSwitch->whichChild.setValue((md->sketchSwitch->whichChild.getValue() == 0 ? -1 : 0)); //-1: invisible, 0: visible
	else if (SO_KEY_PRESS_EVENT(theEvent, S))
		md->strokesSwitch->whichChild.setValue((md->strokesSwitch->whichChild.getValue() == 0 ? -1 : 0)); //-1: invisible, 0: visible
	else if (SO_KEY_PRESS_EVENT(theEvent, T))
		md->sketchSampleSwitch->whichChild.setValue((md->sketchSampleSwitch->whichChild.getValue() == 0 ? -1 : 0)); //-1: invisible, 0: visible

	eventCB->setHandled();
}
/*bool leftButtonDown = false, rightButtonDown = false; //mouse button, e.g. for drag detectioN
float xOld, yOld; //displacements in x and y axes for mouse motion
void mouseEvent(void* userData, SoEventCallback* eventCB)
{
	MyData* md = (MyData*) userData;
	const SoEvent* theEvent = eventCB->getEvent();

	// On mouse press and release just remember the state change
	if (SO_MOUSE_PRESS_EVENT(theEvent, BUTTON1) || SO_MOUSE_PRESS_EVENT(theEvent, BUTTON2)) //left click: BUTTON1, right: BUTTON2, middle: BUTTON3, any: ANY
	{
		//drag detection
		if (SO_MOUSE_PRESS_EVENT(theEvent, BUTTON1))
			leftButtonDown = true;
		else
			rightButtonDown = true;

		//decide which point is clicked/picked, if any
		SbVec2f pos = theEvent->getNormalizedPosition( md->myViewer->getViewportRegion() );
		cout << "pnt on screen: " << pos[0] << "\t" << pos[1] << "\n";
		xOld = pos[0];
		yOld = pos[1];
//md->root->addChild( md->painter->get1PointSep(0.0f, 0.0f, 0.0f) );
md->root->addChild( md->painter->get1PointSep(pos[0], pos[1], 0.0f) );
//md->root->addChild( md->painter->getSphereSep(pos[0], pos[1], 0.0f, 0.01f) );
	}
	else if ((SO_MOUSE_RELEASE_EVENT(theEvent, BUTTON1) || SO_MOUSE_RELEASE_EVENT(theEvent, BUTTON2)))
		leftButtonDown = rightButtonDown = false;
	//mouse move (only interesting if button is down, i.e. mouse dragging
	else if (leftButtonDown || rightButtonDown)
	{
		SbVec2f pos = theEvent->getNormalizedPosition( md->myViewer->getViewportRegion() ); //bottom-left corner is (0,0), top-right corner is (1,1); so x++ as go right, y++ as go up			
		float deltaX = pos[0] - xOld, deltaY = pos[1] - yOld, deltaZ = 0.0f;
		if (leftButtonDown)
		{
			cout << "left drag: " << pos[0] << "\t" << pos[1] << "\n";
md->root->addChild( md->painter->get1PointSep(pos[0], pos[1], -1.0f) );
		}
		else
		{
			cout << "right drag: " << pos[0] << "\t" << pos[1] << "\n";
		}
		xOld = pos[0];
		yOld = pos[1];
	}

	eventCB->setHandled();
}*/
int main(int argc, char ** argv)
{
	// Initializes SoWin library (and implicitly also the Coin
	// library). Returns a top-level / shell window to use.
	HWND mainwin = SoWin::init(argc, argv, argv[0]);
	int winW = 800, winH = 600, inputDB = MCGILL;

	// Make a dead simple scene graph by using the Coin library, only
	// containing a single yellow cone under the scenegraph root.
	SoSeparator* root = new SoSeparator();
	root->ref();

	bool xmlSketch = false; //true: fread sparse sketch in xml w/ stroke information; false: fread dense sketch in dat w/o stroke information
	double start_time = get_time();
	int nSamplesM = 6, nSamplesS = nSamplesM; //# of samples on mesh and sketch
//nSamplesS = nSamplesM = 8; //special case for centaur crab and spider; it always has 8 extremeties
	Mesh* sketch = new Mesh(nSamplesS, true), * mesh = NULL;
	if (xmlSketch)
		sketch->loadSketch(8);	//loads sketch from xml file w/ stroke information; # of sketch pnts is sparse in this case, e.g. 80; id 4+ = footballer
	else
		sketch->loadSketchDense(); //loads sketch from dat file w/o any stroke information; # of sketch pnts is dense in this case, e.g. 1200
	sketch->preprocessSketch(); //disable while-loop just-below to see evenly-spaced dense samples (RETRIEVAL_APP undef mode)
	while ( sketch->computeEvenlySpacedSamplesSketch() ) //disconnection detected
		sketch->setStrokeConnectivity(); //try to add more virtual strokes by auto-increased `close' variable*/
#ifdef RETRIEVAL_APP
	vector< Mesh* > meshes;
	float minDisto = INF;
	int minDistoIdx = -1;
	if (inputDB == TOSCA)
		for (int t = 0; t < 8; t++) //all 8 types in TOSCA set
		{
			int nObjs = 11; //# objects of type t
			if (t == 1)			nObjs = 6; //TOSCA centaur
			else if (t == 2)	nObjs = 7; //TOSCA david
			else if (t == 3)	nObjs = 9; //TOSCA dog
			else if (t == 4)	nObjs = 4; //TOSCA gorilla
			else if (t == 5)	nObjs = 4; //TOSCA horse
			else if (t == 6)	nObjs = 12; //TOSCA victoria
			else if (t == 7)	nObjs = 3; //TOSCA wolf
			for (int i = 0; i < nObjs; i++) //10 types of TOSCA cats
			{
				int idx = (int) meshes.size();
				meshes.push_back(new Mesh(nSamplesM, -(t+1), inputDB, i));
				meshes[idx]->loadOff(TOSCA, t);
				meshes[idx]->computeEvenlySpacedSamples();
				float disto = meshes[idx]->matchWith(sketch);
				if (disto < minDisto)
				{
					minDisto = disto;
					minDistoIdx = idx;
				}
			}
		}
	else if (inputDB == MCGILL)
		for (int i = 0; i < 10; i++) //10 types of McGill articulated database objects
		{
			meshes.push_back(new Mesh(nSamplesM, i, inputDB));
			meshes[i]->loadOff(MCGILL, i);
			meshes[i]->computeEvenlySpacedSamples();
			float disto = meshes[i]->matchWith(sketch);
			if (disto < minDisto)
			{
				minDisto = disto;
				minDistoIdx = i;
			}
//if (i == 2)break;//disable if (disto < minDisto) statement above
		}
	else
		for (int i = 0; i < 20; i++) //20 types of watertight'07 database objects
		{
			meshes.push_back(new Mesh(nSamplesM, i, inputDB));
			meshes[i]->loadOff(WATERTIGHT);
			meshes[i]->computeEvenlySpacedSamples();
			float disto = meshes[i]->matchWith(sketch);
			if (disto < minDisto)
			{
				minDisto = disto;
				minDistoIdx = i;
			}
		}
//minDistoIdx = 3;
	mesh = meshes[minDistoIdx];
	cout << "\n>>>>>> " << get_time()-start_time << " secs for 2.5D matching w/ all database models\n";
#else
/*	mesh = new Mesh(nSamplesM);
	mesh->loadPly(64);//3);//32);//20);//17);//19);
	mesh->computeEvenlySpacedSamples();
	mesh->matchWith(sketch);//*/
	cout << "\n>>>>>> " << get_time()-start_time << " secs for 2.5D matching w/ 1 model\n";
#endif

	Painter* painter = new Painter((strcmp(sketch->classStr, "human") == 0 ? 6.0f : 6.0f) * sketch->spacing / 10.0f);
	float deltaX = 200.0f; //for better visualization
	//////////////// sketch ////////////////
	SoSwitch* sketchSwitch = new SoSwitch;
	sketchSwitch->addChild( painter->getPointsSep(sketch) ); //directly from pnts[]
	//sketchSwitch->addChild( painter->getPointsSep(sketch, true) ); //stroke-by-stroke
	sketchSwitch->whichChild.setValue(0);
	root->addChild(sketchSwitch); //draw sketch
	//////////////// samples on sketch ////////////////
	SoSwitch* sketchSampleSwitch = new SoSwitch;
	sketchSampleSwitch->addChild( painter->getSpheresSepSketch(sketch) );
	sketchSampleSwitch->whichChild.setValue(0);
	root->addChild(sketchSampleSwitch);
	//////////////// strokes on sketch ////////////////
	SoSwitch* strokesSwitch = new SoSwitch;
	strokesSwitch->addChild( painter->getStrokesSepSketch(sketch) );
	strokesSwitch->whichChild.setValue(0);
	root->addChild(strokesSwitch);
	if (mesh)
	{
		/////////////// mesh ///////////////////
		SoSwitch* meshSwitch = new SoSwitch;
		meshSwitch->addChild( painter->getShapeSeparator(mesh, deltaX) );
		meshSwitch->whichChild.setValue(0); //0: visible, -1: invisible
		root->addChild(meshSwitch);
		//////////////// samples on mesh ////////////////
		SoSwitch* meshSampleSwitch = new SoSwitch;
		meshSampleSwitch->addChild( painter->getSpheresSep(mesh, deltaX) );
		meshSampleSwitch->whichChild.setValue(0);
		root->addChild(meshSampleSwitch);
		//////////////// matching samples from sketch and mesh ////////////////
		SoSwitch* matchingSamplesSwitch = new SoSwitch;
		matchingSamplesSwitch->addChild( painter->getMatchingSpheresSep(sketch, mesh, deltaX) );
		matchingSamplesSwitch->whichChild.setValue(0); //0: visible, -1: invisible
		root->addChild(matchingSamplesSwitch);
		//////////////// matching samples from sketch and mesh connected by lines ////////////////
		SoSwitch* matchingSamplesLineSwitch = new SoSwitch;
		matchingSamplesLineSwitch->addChild( painter->getMatchingLinesSep(sketch, mesh, deltaX) );
		matchingSamplesLineSwitch->whichChild.setValue(0); //0: visible, -1: invisible
		root->addChild(matchingSamplesLineSwitch);
	}
//root->addChild( painter->getStrokeSep(sketch, 4) );
//root->addChild( painter->get1PointSep(0.5f, 0.5f, 0.0f) );
//root->addChild( painter->get1PointSep(sketch, 0, true) );root->addChild( painter->get1PointSep(sketch, 23, true) );root->addChild( painter->get1PointSep(sketch, 45, true) );root->addChild( painter->get1PointSep(sketch, 61, true) );
//root->addChild( painter->get1PointSep(sketch, 6, true) );root->addChild( painter->get1PointSep(sketch, 7, true) );
//root->addChild( painter->get1PointSep(sketch, sketch->endpnts[9]->idx, true) );
//root->addChild( painter->get1PointSep(sketch, 147, true) );root->addChild( painter->get1PointSep(sketch, 559, true) );
//root->addChild( painter->get1PointSep(mesh, 6575, false, deltaX) );//root->addChild( painter->get1PointSep(mesh, 4762, false, deltaX) );
/*root->addChild( painter->get1PointSep(sketch, 519, true) );root->addChild( painter->get1PointSep(sketch, 669, true) );
root->addChild( painter->get1PointSep(sketch, 534, true) );root->addChild( painter->get1PointSep(sketch, 669, true) );
root->addChild( painter->get1PointSep(sketch, 549, true) );root->addChild( painter->get1PointSep(sketch, 654, true) );
root->addChild( painter->get1PointSep(sketch, 564, true) );root->addChild( painter->get1PointSep(sketch, 639, true) );
root->addChild( painter->get1PointSep(sketch, 579, true) );root->addChild( painter->get1PointSep(sketch, 624, true) );
root->addChild( painter->get1PointSep(sketch, 594, true) );root->addChild( painter->get1PointSep(sketch, 609, true) );//*/
//root->addChild( painter->get1PointSep(sketch, 0, true) );root->addChild( painter->get1PointSep(sketch, sketch->pnts.size()-1, true) );//*/
//for (int i = 0; i < 95; i++) { int a = 2538; cout << a+i << "\t"; root->addChild( painter->get1PointSep(sketch, a+i, true) ); }//*/

	SoWinExaminerViewer* eviewer = new SoWinExaminerViewer(mainwin);
	struct MyData* md = new MyData();
/*	md->mdsSwitch = mdsSwitch;
	md->meshSwitch = meshSwitch;
	md->mesh2Switch = mesh2Switch;
	md->samplesSwitch = samplesSwitch;
	md->samples2Switch = samples2Switch;
	md->mesh = mesh;*/
	md->root = root;
	md->painter = painter;
	md->myViewer = eviewer;
	md->sketchSwitch = sketchSwitch;
	md->strokesSwitch = strokesSwitch;
	md->sketchSampleSwitch = sketchSampleSwitch;

	//keyboard event callback
	SoEventCallback* eventCB = new SoEventCallback;
	eventCB->addEventCallback(SoKeyboardEvent::getClassTypeId(), keyHit, md);
	root->addChild(eventCB);

/*	//mouse event callback
	SoEventCallback* mouseEventCB = new SoEventCallback;
  	mouseEventCB->addEventCallback(SoMouseButtonEvent::getClassTypeId(), mouseEvent, md);
	//add a mouse event callback to catch mouse motion
	mouseEventCB->addEventCallback(SoLocation2Event::getClassTypeId(), mouseEvent, md);	
	root->addChild(mouseEventCB);*/

	//viewer	
	eviewer->setSize(SbVec2s(winW, winH)); //600, 450
	eviewer->setTitle("2.5D Sketch vs. 3D Shape Matching");
	eviewer->setBackgroundColor(SbColor(1.0f, 1.0f, 1.0f));
	eviewer->setSceneGraph(root);
//	eviewer->getCamera()
	eviewer->show();
	SoWin::show(mainwin); SoWin::mainLoop();
	delete eviewer;
	root->unref();
	return 0;
}
