// Copyright (C) 2013 mocchi

#define NOMINMAX
#include <irrlicht.h>
#include "driverChoice.h"

#include "CCameraSceneNodeRH.h"
#include "CGridSelector.h"
#include "CONObjSceneNode.h"

#include "OnIrrConvert.h"

#include "opennurbs.h"
//#include "ONGEO.h"
#include <vector>
#include <cmath>
#include <limits>
#include <map>

using namespace irr;
using namespace irr::core;
using namespace irr::scene;
using namespace irr::video;

#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

// type ... 2: Lines、3: Triangles
bool ExtractIntersectedElements(const ON_BoundingBox &bb, const S3DVertex *vertices, u32 numVertices, const u32 *indices, u32 numIndices, s32 type, array<u32> &extracted){
	if (type < 2 || type > 3) return false;
	array<bool> bVertices(numVertices);
	bVertices.set_used(numVertices);

	for (u32 i = 0; i < numVertices; ++i){
		ON_3dPoint pos;
		Convert(vertices[i].Pos, pos);
		bVertices[i] = bb.IsPointIn(pos);
	}

	for (u32 ii = 0; ii < numIndices; ii += type){
		bool inside = false;
		for (int h = 0; h < type && !inside; ++h){
			if (bVertices[indices[ii+h]]) inside = true;
		}
		if (!inside) continue;
		for (int h = 0; h < type; ++h) extracted.push_back(indices[ii+h]);
	}
	return true;
}
inline double ProjectPointToLine(const ON_2dPoint &pq, const ON_2dPoint &pl, const ON_2dVector &v1){
	return ON_DotProduct((pq - pl), v1);
}
inline bool Intersect2Lines(const ON_2dPoint &p11, const ON_2dPoint &p12, const ON_2dPoint &p21, const ON_2dPoint &p22, ON_2dPoint &po){
	// 直線の方程式 ax - by + c = 0
	// l1: a1*x - b1*y + c1 = 0
	// l2: a2*x - b2*y + c2 = 0

	double a1 = p12.y - p11.y, b1 = p12.x - p11.x, c1 = p12.x*p11.y - p11.x*p12.y;
	double a2 = p22.y - p21.y, b2 = p22.x - p21.x, c2 = p22.x*p21.y - p21.x*p22.y;
	if (-ON_ZERO_TOLERANCE < a1 && a1 < ON_ZERO_TOLERANCE){ // a1 = 0のとき
		// l1: - b1*y + c1 = 0 => y = c1 / b1
		// l2: a2*x - b2*y + c2 = 0 => x = (b2*y - c2) / a2
		if (-ON_ZERO_TOLERANCE < b1 && b1 < ON_ZERO_TOLERANCE) return false;
		if (-ON_ZERO_TOLERANCE < a2 && a2 < ON_ZERO_TOLERANCE) return false;
		po.y = c1 / b1;
		po.x = (b2*po.y - c2) / a2;
	}else{ // a1 != 0のとき
		// a2/a1*l1     : a2*x - b1*a2/a1*y + c1*a2/a1      = 0
		//       l2     : a2*x -       b2*y +       c2      = 0
		// a2/al*l1 - l2: (b2 - b1*a2/a1)*y + c1*a2/a1 - c2 = 0
		//  => y = (c2 - c1*a2/a1)/(b2 - b1*a2/a1)
		//       = (a1*c2 - c1*a2)/(a1*b2 - b1*a2)
		double denom = a1*b2 - b1*a2;
		if (-ON_ZERO_TOLERANCE < denom && denom < -ON_ZERO_TOLERANCE) return false;
		po.y = (a1*c2 - c1*a2) / denom;
		po.x = (b1*po.y - c1) / a1;
	}
	return true;
}

bool ExtractIntersectedElements(const line3df &lin_, f32 thicknes_, const S3DVertex *vertices, u32 numVertices, const u32 *indices, u32 numIndices, s32 type, array<u32> &extracted, array<f32> &distance){
	if (type < 2 || type > 3) return false;

	ON_Line line;
	Convert(lin_, line);
	ON_3dRay ray;
	ray.m_P = line.from;
	ray.m_V = line.Direction();

	double thickness = thicknes_;
	double thickness2 = thickness * thickness;

	// plnに投影した三角形との接触判定
	ON_Plane pln(line.from, line.Tangent());
	std::vector<bool> usedVtx(numVertices);
	ON_SimpleArray<ON_3dVector> pt_proj;
	pt_proj.SetCapacity(numVertices);
	pt_proj.SetCount(numVertices);
	for (u32 i = 0; i < numIndices; ++i) usedVtx[indices[i]] = true;

	for (u32 i = 0; i < numVertices; ++i){
		if (!usedVtx[i]) continue;
		ON_3dPoint pt;
		Convert(vertices[i].Pos, pt);
		ON_3dVector v = pt - pln.origin;
		pt_proj[i].Set(ON_DotProduct(pln.xaxis, v), ON_DotProduct(pln.yaxis, v), ON_DotProduct(pln.xaxis, v));
	}

	if (type == 3){
		for (u32 i3 = 0; i3 < numIndices; i3 += 3){
			ON_2dVector pt[3];
			for (int h = 0; h < 3; ++h) pt[h] = ON_2dVector(pt_proj[indices[i3+h]]);
			if (pt[0].x < -thickness && pt[1].x < -thickness && pt[2].x < -thickness) continue;
			if (pt[0].x >  thickness && pt[1].x >  thickness && pt[2].x >  thickness) continue;
			if (pt[0].y < -thickness && pt[1].y < -thickness && pt[2].y < -thickness) continue;
			if (pt[0].y >  thickness && pt[1].y >  thickness && pt[2].y >  thickness) continue;

			ON_2dVector v[3];
			v[0] = pt[1] - pt[0], v[1] = pt[2] - pt[1], v[2] = pt[0] - pt[2];
			bool cw = (ON_CrossProduct(v[0], v[1])[2] < 0); // cw=true : 右回り
#if 0
			if (cw){ // 右回りのときは左回りに修正
				std::swap(pt[1], pt[2]);
				v[0] = pt[1] - pt[0], v[1] = pt[2] - pt[1], v[2] = pt[0] - pt[2];
			}
#endif
			ON_2dVector n[3];
			bool isZero[3];
			int zeroCount = 0;
			double length2[3], length[3];
			for (int h = 0; h < 3; ++h){
				isZero[h] = ((length2[h] = v[h].LengthSquared()) < ON_ZERO_TOLERANCE);
				if (isZero[h]) ++zeroCount;
			}
			for (int h = 0; h < 3; ++h){
				if (isZero[h]) continue;
				length[h] = std::sqrt(length2[h]);
				v[h] /= length[h];

				n[h].Set(v[h].y, -v[h].x); // 法線を三角形の外側に向ける
				if (cw) n[h].Reverse();
			}

			// 条件を満たさない場合はcontinueで次のTriangleに処理を移す。
			if (zeroCount >= 2){ // 三角形が投影平面で点に見えるとき
				ON_2dVector &ptd = (isZero[0]) ? pt[0] : pt[1];
				if (ptd.LengthSquared() > thickness2) continue;
			}else if (zeroCount == 1){ // 三角形が投影平面で線分に見えるとき
				ON_2dVector &pt1 = pt[0];
				ON_2dVector &pt2 = (isZero[0]) ? pt[2] : pt[1];
				ON_2dVector vl = (isZero[0]) ? -v[2] : v[1];
				double dist = std::abs(ON_CrossProduct(-pt1,vl)[2]);
				if (dist > thickness) continue;
			}else{ // 三角形が投影平面で線分に見えるとき
				bool near_to_point = false;
				for (int h = 0; h < 3 && !near_to_point; ++h){
					if (pt[h].LengthSquared() < thickness2) near_to_point = true;
				}
				if (!near_to_point){
					for (int h = 0; h < 3; ++h) n[h] *= thickness;
					ON_2dPoint ptth[3];
					Intersect2Lines(ON_2dPoint(pt[0]+n[0]), ON_2dPoint(pt[1]+n[0]), ON_2dPoint(pt[1]+n[1]), ON_2dPoint(pt[2]+n[1]), ptth[0]);
					Intersect2Lines(ON_2dPoint(pt[1]+n[1]), ON_2dPoint(pt[2]+n[1]), ON_2dPoint(pt[2]+n[2]), ON_2dPoint(pt[0]+n[2]), ptth[1]);
					Intersect2Lines(ON_2dPoint(pt[2]+n[2]), ON_2dPoint(pt[0]+n[2]), ON_2dPoint(pt[0]+n[0]), ON_2dPoint(pt[1]+n[0]), ptth[2]);
					//       p1
					//       *
					//      /|＼
					//     / |O ＼
					//    /  x    ＼
					// p2/_-~       ＼p3
					//  *-------------*
					//  p1->p2->p3、p1->p2->O、p2->p3->O、p3->p1->Oの向きが全て等しいとき、三角形の中に入っている。
					bool cwth[] = {
						(ON_CrossProduct(-ON_2dVector(ptth[1]), ON_2dVector(ptth[0]))[2] < 0),
						(ON_CrossProduct(-ON_2dVector(ptth[2]), ON_2dVector(ptth[1]))[2] < 0),
						(ON_CrossProduct(-ON_2dVector(ptth[0]), ON_2dVector(ptth[2]))[2] < 0)
					};
					if (cwth[0] != cw || cwth[1] != cw || cwth[2] != cw) continue;
				}
			}

			// 三角形の中に入っている条件を満たしたときだけここを通る
			// 三角形の頂点インデックスを出力
			for (int h = 0; h < 3; ++h){
				extracted.push_back(indices[i3+h]);
			}
			// 三角形が接する無限平面と直線との交点までの距離を出力
			{
				ON_3dPoint pts[3];
				for (int h = 0; h < 3; ++h) Convert(vertices[indices[i3+h]].Pos, pts[h]);
				ON_Plane pln(pts[0], pts[1], pts[2]);

				double dist;
				ON_Intersect(line, pln, &dist);
				dist *= line.Length();

				distance.push_back(static_cast<f32>(dist));
			}
		}
	}else if(type == 2){
		// Todo:
	}
	return true;
}

bool MoveCamera_Pan(s32 curX, s32 curY, s32 prevX, s32 prevY, f32 cameraWidth, f32 cameraHeight, s32 windowWidth, s32 windowHeight,
		const vector3df &upVector, vector3df &targetPos, vector3df &eyePos){

	f64 dx = static_cast<f64>(curX - prevX);
	f64 dy = static_cast<f64>(curY - prevY);
	vector3df eyeVec = (targetPos - eyePos).normalize();
	vector3df rightVec = upVector.crossProduct(eyeVec).normalize();
	f64 rate = static_cast<f64>(cameraWidth) / static_cast<f64>(windowWidth);
	vector3df dUX = rightVec * static_cast<f32>(dx * rate);
	vector3df dUY = upVector * static_cast<f32>(dy * rate);
	targetPos += (dUX + dUY);
	eyePos += (dUX + dUY);
	return true;
}

vector3df RotateByAxis(f64 rad, const vector3df &vec, const vector3df &axis){
	f64 cos_2 = std::cos(rad*0.5), sin_2 = std::sin(rad*0.5);
	quaternion q(sin_2 * axis.X, sin_2 * axis.Y, sin_2 * axis.Z, cos_2);
	quaternion qc(-q.X, -q.Y, -q.Z, q.W);

	quaternion po = q * quaternion(vec.X, vec.Y, vec.Z, 0) * qc;
	return vector3df(po.X, po.Y, po.Z);
}

#if 0
bool MoveCamera_RotateXY(s32 curX, s32 curY, s32 prevX, s32 prevY, f32 cameraWidth, f32 cameraHeight, s32 windowWidth, s32 windowHeight,
		vector3df &upVector, const vector3df &targetPos, vector3df &eyePos){

	f64 rw = 2.0 / static_cast<f64>(windowWidth);
	f64 rh = 2.0 / static_cast<f64>(windowWidth);

	vector3df eyeVec = (targetPos - eyePos);
	f64 dist = static_cast<f64>(eyeVec.getLength());
	eyeVec = eyeVec.normalize();

	f64 rotAngX = std::atan2(static_cast<f64>(curX - prevX) * rw, 0.5);
	f64 rotAngY = std::atan2(static_cast<f64>(curY - prevY) * rh, 0.5);

	eyeVec = RotateByAxis(rotAngX, eyeVec, upVector);
	vector3df rightVec = eyeVec.crossProduct(upVector).normalize();
	eyeVec = RotateByAxis(rotAngY, eyeVec, rightVec);

	upVector = rightVec.crossProduct(eyeVec).normalize();
	eyePos = targetPos - eyeVec * static_cast<f32>(dist);
	return true;
}
#else
bool MoveCamera_RotateXY(s32 curX, s32 curY, s32 prevX, s32 prevY, f32 cameraWidth, f32 cameraHeight, s32 windowWidth, s32 windowHeight,
		vector3df &upVector, const vector3df &targetPos, vector3df &eyePos){

	f64 rw = 2.0 / static_cast<f64>(windowWidth);
	f64 rh = 2.0 / static_cast<f64>(windowHeight);

	vector3df eyeVec = (targetPos - eyePos);
	vector3df rightVec = upVector.crossProduct(eyeVec).normalize();
	f64 dist = static_cast<f64>(eyeVec.getLength());
	eyeVec /= dist;
	upVector.normalize();

	vector3df dragDir = (rightVec * static_cast<f64>(prevX - curX) + upVector * static_cast<f64>(prevY - curY));
	f64 rotAng = std::atan2(static_cast<f64>(dragDir.getLength()) * rw, 0.5);
	vector3df axisDir = dragDir.crossProduct(eyeVec).normalize();

	eyeVec = RotateByAxis(rotAng, eyeVec, axisDir).normalize();
	upVector = RotateByAxis(rotAng, upVector, axisDir).normalize();
	eyePos = targetPos - eyeVec * static_cast<f32>(dist);
	return true;
}
#endif

class App : public IEventReceiver
{
public:
	// We'll create a struct to record info on the mouse state
	struct SMouseState
	{
		core::position2di Position;
		bool LeftButtonDown, MiddleButtonDown, RightButtonDown;
		SMouseState() : LeftButtonDown(false), MiddleButtonDown(false), RightButtonDown(false) { }
	} MouseState;
	f32 CameraWidth, CameraHeight;
	s32 WindowWidth, WindowHeight;
	vector3df EyePos, TargetPos, UpVector;

	// === Implement of IEventReceiver ===
	virtual bool OnEvent(const SEvent& event)
	{
		// Remember the mouse state
		if (event.EventType == irr::EET_MOUSE_INPUT_EVENT) {
			switch(event.MouseInput.Event) {
			case EMIE_RMOUSE_PRESSED_DOWN:
				MouseState.RightButtonDown = true;
				break;
			case EMIE_RMOUSE_LEFT_UP:
				MouseState.RightButtonDown = false;
				break;
			case EMIE_MMOUSE_PRESSED_DOWN:
				MouseState.MiddleButtonDown = true;
				break;
			case EMIE_MMOUSE_LEFT_UP:
				MouseState.MiddleButtonDown = false;
				break;
			case EMIE_LMOUSE_PRESSED_DOWN:
				MouseState.LeftButtonDown = true;
				break;

			case EMIE_RMOUSE_DOUBLE_CLICK:
			case EMIE_LMOUSE_LEFT_UP:{
				MouseState.LeftButtonDown = false;
				f32 WindowWidthF = static_cast<f32>(WindowWidth);
				f32 WindowHeightF = static_cast<f32>(WindowHeight);

				f32 CameraWidthF = static_cast<f32>(CameraWidth);
				f32 CameraHeightF = static_cast<f32>(CameraHeight);

				f32 dx = (static_cast<f32>(MouseState.Position.X) / WindowWidthF - 0.5f) * CameraWidthF;
				f32 dy = -(static_cast<f32>(MouseState.Position.Y) / WindowHeightF - 0.5f) * CameraHeightF; 

				ON_BoundingBox bb;
				Selector.getBoundingBox(bb);

				ICameraSceneNode *camera = Smgr->getActiveCamera();
				vector3df Pos = camera->getPosition();
				vector3df ZDir = camera->getTarget() - Pos;
				vector3df YDir = camera->getUpVector();
				vector3df XDir = ZDir.crossProduct(YDir).normalize();
				YDir = YDir.normalize();
				double distEye2Target = ZDir.getLength();
				ZDir /= distEye2Target;

				vector3df p1 = Pos + XDir * dx + YDir * dy;
				ON_PlaneEquation peq;
				peq.Create(ON_3dPoint(p1.X, p1.Y, p1.Z), ON_3dVector(ZDir.X, ZDir.Y, ZDir.Z));

				f64 raylen = bb.MaximumDistanceTo(peq);
				vector3df p2 = p1 + ZDir * static_cast<f32>(raylen);

				array<CGridSelector::QueryResult> results;
				line3df ray(p1, p2);
				ITimer *timer = Device->getTimer();
				u32 ticks[4];
				timer->tick();
				ticks[0] = timer->getTime();
				Selector.queryBoxFromThickRay(ray, 0.125f, true, results);
				timer->tick();
				ticks[1] = timer->getTime();
#if 0
				// dbg
				ONX_Model model_dbg;
				// dbg
#endif
				// とりあえず一番近いSceneNodeを選択
				f32 dist_min = std::numeric_limits<f32>::max();
				CONObjSceneNode *sn_nearest = 0;

				{
					typedef std::map<CONObjSceneNode *, array<u32> > extr_by_bb_map_t;
					extr_by_bb_map_t extr_by_bbs;
					for (u32 j = 0; j < results.size(); ++j){
						CGridSelector::QueryResult &res = results[j];
						printf("Hit %u nodes from grid \n", res.sceneNodes.size());
						for (u32 i = 0; i < res.sceneNodes.size(); ++i){
							CONObjSceneNode *sn = res.sceneNodes[i];
							array<u32> &extr_by_bb = extr_by_bbs[sn];
							ExtractIntersectedElements(res.box, &sn->Vertices[0], sn->Vertices.size(),
								&sn->Indices[0], sn->Indices.size(), 3, extr_by_bb);
						}
					}
					timer->tick();
					ticks[2] = timer->getTime();


					for (extr_by_bb_map_t::iterator iter = extr_by_bbs.begin(); iter != extr_by_bbs.end(); ++iter){
						array<u32> &extr_by_bb = iter->second;
						if (extr_by_bb.size() == 0) continue;
						CONObjSceneNode *sn = iter->first;
						array<u32> extr_by_ray;
						array<f32> distance;
						ExtractIntersectedElements(ray, 0.0625f, &sn->Vertices[0], sn->Vertices.size(),
							&extr_by_bb[0], extr_by_bb.size(), 3, extr_by_ray, distance);
	//					printf("  node:%p\n", sn);

						for (u32 i = 0; i < distance.size(); ++i){
	//						printf("   distance:%f\n", distance[i]);
							if (dist_min > distance[i]) dist_min = distance[i], sn_nearest = sn;
						}
#if 0
						// dbg
						for (u32 i3 = 0; i3 < extr_by_ray.size(); i3 += 3){
							ON_3dPointArray pts;
							for (int h = 0; h < 3; ++h){
								Convert(sn->Vertices[extr_by_ray[i3+h]].Pos, pts.AppendNew());
							}
							pts.Append(*pts.First());
							ONX_Model_Object &obj = model_dbg.m_object_table.AppendNew();
							obj.m_object = new ON_PolylineCurve(pts);
							obj.m_attributes.m_color.SetRGB(255,128,0);
							obj.m_attributes.SetColorSource(ON::color_from_object);
							obj.m_bDeleteObject = false;
						}
						// dbg
#endif
					}
					if (sn_nearest){
						if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP){
							sn_nearest->Selected = (sn_nearest->Selected) ? false : true;
						}else if (event.MouseInput.Event == EMIE_RMOUSE_DOUBLE_CLICK){
							TargetPos = EyePos + ZDir * dist_min + XDir * dx + YDir * dy;
							EyePos = TargetPos - ZDir * distEye2Target;
						}
					}
					timer->tick();
					ticks[3] = timer->getTime();

					printf("time: %d %d %d [msec]\n", ticks[1]-ticks[0], ticks[2]-ticks[1], ticks[1]-ticks[0]);

#if 0
					// dbg
					model_dbg.Write("d:/intersect_tri.3dm", 4);
					// dbg
#endif
				}
//				camera->drop(); // getActiveCameraでとったオブジェクトはdropしてはいけない
				break;
			}

			case EMIE_MOUSE_MOVED:{
				if (MouseState.MiddleButtonDown){
					MoveCamera_Pan(
						event.MouseInput.X, event.MouseInput.Y, MouseState.Position.X, MouseState.Position.Y,
						CameraWidth, CameraHeight, WindowWidth, WindowHeight,
						UpVector, TargetPos, EyePos);
				}
				if (MouseState.RightButtonDown){
					MoveCamera_RotateXY(
						event.MouseInput.X, event.MouseInput.Y, MouseState.Position.X, MouseState.Position.Y,
						CameraWidth, CameraHeight, WindowWidth, WindowHeight,
						UpVector, TargetPos, EyePos);
				}

				LightNodes[0]->getLightData().Direction = (TargetPos - EyePos).normalize();
				LightNodes[0]->getLightData().Position = EyePos;
				MouseState.Position.X = event.MouseInput.X;
				MouseState.Position.Y = event.MouseInput.Y;
				break;
			}

			case EMIE_MOUSE_WHEEL:{
				f32 r = (event.MouseInput.Wheel == -1.0f) ? 1.15f : 0.85f;
				CameraWidth *= r;
				CameraHeight *= r;
				break;
			}

			default:
				// We won't use the wheel
				break;
			}
		}
		return false;
	}

	const SMouseState & GetMouseState(void) const {
		return MouseState;
	}

	bool Initialize(){
		// ask user for driver
		video::E_DRIVER_TYPE driverType=driverChoiceConsole();
		if (driverType==video::EDT_COUNT) return false;

		// CameraWidth : CameraHeight = WindowWidth : WindowHeight に
		// しないと表示の縦横比が1:1にならない。
//		CameraWidth = 64;
//		CameraHeight = 48;

		WindowWidth = 640;
		WindowHeight = 480;

		// create device
		Device = 0;
		{
			SIrrlichtCreationParameters p;
			p.DriverType = driverType;
			p.WindowSize = core::dimension2d<u32>(WindowWidth, WindowHeight);
			p.Bits = 16;
			p.Fullscreen = false;
			p.Stencilbuffer = false;
			p.Vsync = false;
			p.EventReceiver = this;
			p.WindowId = 0;

			Device = createDeviceEx(p);
		}

		if (Device == 0) return false; // could not create selected driver.

		// create engine

		Device->setWindowCaption(L"Custom Scene Node - Irrlicht Engine Demo");
		Device->setResizable(true);

		Driver = Device->getVideoDriver();
		Smgr = Device->getSceneManager();

		return true;
	}

	~App(){
		Device->drop();
	}

	// ためしにつくったメソッド
	void createSceneNodes(ONX_Model &model){

	//	const irr::scene::IGeometryCreator *gc = Smgr->getGeometryCreator();
	//	irr::scene::IMesh *cmesh = gc->createCubeMesh(irr::core::vector3df(80.0f,80.0f,200.0f));
	//	Smgr->addMeshSceneNode(cmesh, Smgr->getRootSceneNode());
	//	cmesh->drop();

		aabbox3d<f32> bbox;

		int count = 0;
		CustomNodes.clear();

		for (s32 j = 0; j < model.m_object_table.Count(); ++j){
			const ON_Mesh *mesh = ON_Mesh::Cast(model.m_object_table[j].m_object);
			const ON_Brep *brep = ON_Brep::Cast(model.m_object_table[j].m_object);
			if (!mesh && !brep) continue;

			CONObjSceneNode *myNode = (mesh) ? 
				new CONObjSceneNode(&model, mesh, model.m_object_table[j].m_attributes, Smgr->getRootSceneNode(), Smgr, 666) :
				new CONObjSceneNode(&model, brep, model.m_object_table[j].m_attributes, Smgr->getRootSceneNode(), Smgr, 666);

			aabbox3d<f32> bbox_cur = myNode->getBoundingBox();
			if (j == 0) bbox = bbox_cur;
			else bbox.addInternalBox(bbox_cur);

			CustomNodes.push_back(myNode);
		}

		Selector.Initialize(bbox.getExtent().getLength()/8.0f, bbox);
//		Selector.Initialize(.125f, bbox);

		for (u32 j = 0; j < CustomNodes.size(); ++j){
			CONObjSceneNode *n = CustomNodes[j];
			if (!n->IsSelectable) continue;
			Selector.addTriangles(n, &n->Indices[0], &n->Vertices[0], n->Indices.size());
		}

#if 0
		// debug
		// create grid mesh for display
		{
			ON_Mesh mesh;
			Selector.getGridMesh(mesh);

			ON_3dmObjectAttributes att;
			att.m_color.SetAlpha(128);
			CONObjSceneNode *myNode = new CONObjSceneNode(&mesh, att, model.m_layer_table, Smgr->getRootSceneNode(), Smgr, 666); 
			myNode->IsSelectable = false;
			myNode->Material.Lighting = false;
		}

		// debug
#endif

		// create camera
	//	irr::scene::ICameraSceneNode * camera = Smgr->addCameraSceneNode();

		ICameraSceneNode* camera = new CCameraSceneNodeRH(Smgr->getRootSceneNode(), Smgr, 0);
//		camera->bindTargetAndRotation(true);
		Smgr->setActiveCamera(camera);
		camera->drop();

		matrix4 proj;
	//	proj.buildProjectionMatrixOrthoRH(
	//		bbox.MaxEdge.getDistanceFrom(bbox.MinEdge), bbox.MaxEdge.getDistanceFrom(bbox.MinEdge), -300.0f, 300.0f);
		double diag2 = bbox.getExtent().getLengthSQ();
		double ww_wh = static_cast<double>(WindowWidth) / static_cast<double>(WindowHeight);
		double ch = std::sqrt(diag2 / (1 + ww_wh * ww_wh));
		CameraHeight = static_cast<s32>(ch);
		CameraWidth = static_cast<s32>(ch * ww_wh);

		proj.buildProjectionMatrixOrthoRH(CameraWidth, CameraHeight, -10000.0f, 10000.0f);

		EyePos = bbox.getCenter() - vector3df(0.0f, 10.0f, 0.0f);
		TargetPos = bbox.getCenter();
		UpVector.set(0,0,1);

		camera->setProjectionMatrix(proj, true);
		camera->setTarget(TargetPos);
		camera->setPosition(EyePos);
		camera->setUpVector(UpVector);

		/*
		The second special effect is very basic, I bet you saw it already in
		some Irrlicht Engine demos: A transparent billboard combined with a
		dynamic light. We simply create a light scene node, let it fly around,
		and to make it look more cool, we attach a billboard scene node to it.
		*/

		// create light
		ILightSceneNode* lightNode = Smgr->addLightSceneNode(0, camera->getPosition(),
					video::SColorf(1.0f, 1.0f, 1.0f, 1.0f), 1000.0f);
		lightNode->getLightData().Direction.set(-1.0f, 0.0f, 0.0f);
		lightNode->getLightData().Attenuation.set(1.0f, 0.0f, 0.0f);
		lightNode->getLightData().AmbientColor.set(1.0f, 0.0f, 0.0f, 0.0f);
		lightNode->getLightData().DiffuseColor.set(1.0f, 1.0f, 1.0f, 1.0f);
		lightNode->getLightData().SpecularColor.set(1.0f, 1.0f, 1.0f, 1.0f);
		lightNode->setLightType(irr::video::ELT_DIRECTIONAL);
		LightNodes.push_back(lightNode);

#if 0
		ISceneNodeAnimator* anim = 0;
		anim = Smgr->createFlyCircleAnimator (vector3df(0,1500,0),1000.0f, 0.0008f);
		lightNode->addAnimator(anim);
		anim->drop();
#endif
#if 0
		{
			ISceneNodeAnimator* anim = 0;
			anim = Smgr->createFlyCircleAnimator (
				camera->getTarget(), camera->getTarget().getDistanceFrom(camera->getPosition()),
				0.0005f, camera->getUpVector());
			camera->addAnimator(anim);
			lightNode->addAnimator(anim);
			anim->drop();
		}
#endif
	}

	void Run(){
		/*
		Now draw everything and finish.
		*/
		u32 frames=0;
		while(Device->run())
		{
			rect<s32> vp = Driver->getViewPort();
			WindowWidth = vp.getWidth(), WindowHeight = vp.getHeight();

			{
				double cw = static_cast<double>(CameraWidth), ch = static_cast<double>(CameraHeight);
				double diag2 = cw * cw + ch * ch;
				double ww_wh = static_cast<double>(WindowWidth) / static_cast<double>(WindowHeight);
				ch = std::sqrt(diag2 / (1 + ww_wh * ww_wh));
				CameraHeight = static_cast<s32>(ch);
				CameraWidth = static_cast<s32>(ch * ww_wh);
			}

//			CameraWidth = static_cast<f32>(vp.getWidth()) / 10.0f, CameraHeight = static_cast<f32>(vp.getHeight()) / 10.0f;
			{
				matrix4 proj;
				proj.buildProjectionMatrixOrthoRH(CameraWidth, CameraHeight, -100.0f, 100.0f);
				ICameraSceneNode *camera = Smgr->getActiveCamera();
#if 1
				camera->setTarget(TargetPos);
				camera->setPosition(EyePos);
				camera->setUpVector(UpVector);
				camera->setProjectionMatrix(proj, true);
#endif
			}
			Driver->beginScene(true, true, SColor(0,30,30,30));

			matrix4 mat = Driver->getTransform(ETS_PROJECTION);

			Smgr->drawAll();

			Driver->endScene();
			if (++frames==100)
			{
				core::stringw str = L"Irrlicht Engine [";
				str += Driver->getName();
				str += L"] FPS: ";
				str += (s32)Driver->getFPS();

				Device->setWindowCaption(str.c_str());
				frames=0;
			}
			Device->sleep(10);
	//		Device->yield();
		}
	}

private:
	IrrlichtDevice *Device;
	IVideoDriver *Driver;
	ISceneManager *Smgr;

	CGridSelector Selector;
	array<ILightSceneNode *> LightNodes;
	array<CONObjSceneNode *> CustomNodes;
};

/*
That's it. The Scene node is done. Now we simply have to start
the engine, create the scene node and a camera, and look at the result.
*/
int main(int argc, char *argv[])
{
	ONX_Model model;
	if (argc >= 2){
		model.Read(argv[1]);
	}else{
		return 0;
	}

	App app;
	app.Initialize();
	app.createSceneNodes(model);
	app.Run();

	return 0;
}

/*
That's it. Compile and play around with the program.
**/
