/********************************************************************/
/* Copyright (c) 2017 System fugen G.K. and Yuzi Mizuno          */
/* All rights reserved.                                             */
/********************************************************************/
#include "MGCLStdAfx.h"

#include "mg/Tolerance.h"
#include "mg/DnameControl.h"
#include "mg/Object.h"
#include "mg/Box.h"
#include "mg/LBRep.h"
#include "mg/SPointSeq.h"
#include "mg/FSurface.h"
#include "mg/Surface.h"
#include "mg/Plane.h"
#include "mg/SBRep.h"
#include "mg/RSBRep.h"
#include "mg/MGStl.h"
#include "mg/PickObjectCB.h"
#include "mg/PickObjectSB.h"
#include "Tl2/TL2Triangles.h"
#include "Tl2/TL2Face.h"
#include "mgGL/Context.h"
#include "mgGL/OpenGLView.h"
#include "mgGL/Appearance.h"
#include "mgGL/Lights.h"
#include "mgGL/Light.h"
#include "mgGL/PlaneImage.h"

//でばっぐよう
#include "mgGL/DirectionalLight.h"

#if defined(_DEBUG)
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

namespace MGCL{

///getCurveCurvatureLength computes the maximum curvature graph length.
///Function's return value is the maximum length of the graph.
double getCurveCurvatureLength(
	const MGCurve& curve, double scale, int density, bool use_radius
){
	assert(density > 0);
	MGUnit_vector T, N, B;	
	double curva, torsion, length=-1.;
	double revdens = 1. / density;
	MGPosition pos(3);
	MGVector v(curve.start_point());  // v : vertex of the hige's polyline
	const MGKnotVector& kv = curve.knot_vector();
	if(!use_radius)
		scale*=-1.;
	int ordr=kv.order();
	if(ordr>0){
	for(int i = kv.order()-1; i < kv.bdim(); i++){
		double t = kv[i];
		const double& tp1 = kv[i+1];
		MGInterval iv(t,tp1);
		double dt = (tp1-t)*revdens;
		for(int j = 0; j < density; j++, t += dt){
			pos = curve.eval(t);     // pos : on the curve
			curve.Frenet_frame(t, T, N, B, curva, torsion);
			// N is from pos to the center of the osculating circle at pos.

			if(use_radius)
				curva=1./curva;
			if(length<curva)
				length=curva;

			v=pos+scale*curva*N;
		}
	}
	}
	pos = curve.end_point();
	curve.Frenet_frame(curve.param_e(), T, N, B, curva, torsion);
	if(use_radius)
		curva=1./curva;
	if(length<curva)
		length=curva;

	v=pos+scale*curva*N;
	return length;
}

}//End of namespace MGCL.

//Make 2 types of display list of this gel(wire and shading).
void MGObject::make_display_list(
	MGCL::VIEWMODE vmode
)const{
	mgVBO* vbo=dlist_name();
	vbo->initializeVBO(vmode);

	const MGDrawParam& para=mgVBOElement::getDrawParam();
	int line_density=para.line_desity_wire_face();
	if(vmode!=MGCL::SHADING){
		mgLightModeSwitcher switcher(*vbo, mgGLSL::NoShading);
		drawWire(*vbo,line_density);
		vbo->setDirty(false,mgVBO::WIRE);
	}
	if(vmode==MGCL::SHADING || vmode==MGCL::WIRE_AND_SHADING){
		if(manifold_dimension()>=2){
			mgLightModeSwitcher switcher(*vbo, mgGLSL::Shading);
			shade(*vbo,para,mgVBO::SHADING);
			vbo->setDirty(false,mgVBO::SHADING);
		}
	}
}

///Make a display list of this gel.
void MGPlane::make_display_list(
	MGCL::VIEWMODE vmode
)const{
	mgVBO* vbo=dlist_name();
	if(vmode==MGCL::SHADING){
		vbo->clearElements(mgVBO::SHADING);
	}else{
		if(vmode!=MGCL::WIRE)
			vmode=MGCL::WIRE_AND_SHADING;
		vbo->clearElements(mgVBO::BOTH);
	}
	vbo->clearStaticAttributes();
	drawAttrib(*vbo,false);

	//Here vmode=WIRE, SHADING, or WIRE_AND_SHADING.
	const MGDrawParam& para=mgVBOElement::getDrawParam();
	int line_density=para.line_desity_wire_face();
	if(vmode!=MGCL::SHADING){
		drawWire(*vbo,line_density);
		vbo->setDirty(false,mgVBO::WIRE);
	}
	if(vmode!=MGCL::WIRE){
		shade(*vbo,para,mgVBO::SHADING);
		vbo->setDirty(false,mgVBO::SHADING);
	}
}

///Process of draw or render attributes.
void MGGroup::drawAttrib(
	mgVBO& vbo,///<The target graphic object.
	bool no_color///<if true, color attribute will be neglected.
)const{
	const MGAppearance* app=appearance();
	if(app){
		app->drawAttrib(vbo,no_color);
	} else {
		const MGContext* pCtx = this->context();
		if(pCtx)
			pCtx->drawAttrib(vbo, no_color);
	}
}

//Make a display list of this gel.
void MGGroup::make_display_list(
	MGCL::VIEWMODE vmode
)const{
	mgVBO* vbo=dlist_name();
	vbo->initializeVBO(MGCL::WIRE_AND_SHADING);

	//Make display list of the gel that has an object.
	std::vector<const MGGroup*> nested_groups;
	MGGroup::const_iterator i,is=begin(), ie=end();	
	for(i=is; i!=ie; i++){
		if((*i)->no_display())
			continue;
		const MGAttribedGel* agel=dynamic_cast<const MGAttribedGel*>(*i);
		if(!agel)
			continue;
		mgVBO* vboi=agel->dlist_name();
		if(!vboi)//This is to judge MGAppearance(MGAppearance must be excluded).
			continue;
		vbo->drawGel(*agel);
		agel->setDirty(true);
		const MGGroup* grp=dynamic_cast<const MGGroup*>(agel);
		if(!grp)
			continue;
		nested_groups.push_back(grp);
	}
	
	///make display list of the nested MGGroup.
	size_t ngroups(nested_groups.size());
	for(size_t j=0; j<ngroups; j++){
		const MGGroup& grpj=*(nested_groups[j]);
		grpj.make_display_list(vmode);
	}
	vbo->setDirty(false);
}

void MGPlane::display_arrows(mgSysGL& sgl)const{
	MGVector U,V;
	get_uv_display_vector(U,V);
	const MGPosition& cen = center();
	MGBox box(cen-U,cen+U);
	box.expand(cen-V);
	box.expand(cen+V);
	MGPosition pos[10], uv=center_param();
	arrow(box,uv[0],uv[1],pos);

	const MGColor& ucolor=MGColor::get_instance(MGColor::Red);
	const MGColor& vcolor=MGColor::get_instance(MGColor::Green);
	const MGColor& white=MGColor::get_instance(MGColor::White);
	ucolor.exec(sgl);
	sgl.drawArrow(&pos[0]);

	pos[3] = pos[0];
	vcolor.exec(sgl);
	sgl.drawArrow(&pos[3]);

	pos[6] = pos[0];			
	white.exec(sgl);
	sgl.drawArrow(&pos[6]);
}

//Shade the object in world coordinates.
void MGStl::shade(
	mgVBO& vbo,
	const MGDrawParam& para,
	mgVBO::ELEMENT_TARGET target
)const{
	mgLightModeSwitcher switcher(vbo, mgGLSL::Shading);
	vbo.drawSTL(*this,target,GL_FILL);
}

//Draw 3D curve in world coordinates.
//The object is converted to curve(s) and is drawn.
void MGStl::drawWire(
	mgVBO& vbo,
	int line_density	//line density to draw a surface in wire mode.
)const{
	mgLightModeSwitcher switcher(vbo, mgGLSL::NoShading);
	vbo.drawSTL(*this,mgVBO::WIRE,GL_LINE);
}

// 三角形ごとの法線ベクトルを表示する
void MGStl::display_arrows(mgSysGL& sgl)const{
	// 三角形の数を取得
	size_t nTriangle(m_vecNormlTriang.size());
	// 矢印描画のための座標値の配列
	MGPosition pos[4];
	// 色を示すインスタンスを生成
	const MGColor& white=MGColor::get_instance(MGColor::White);

	for(int i = 0; size_t(i) < nTriangle; i++){
	// それぞれの三角形の法線方向に矢印を描画
		// 三角形の中点を取得
		int i3 = i*3;
		pos[0] = (m_vecPos[m_indices[i3]]+m_vecPos[m_indices[i3+1]]+m_vecPos[m_indices[i3+2]])/3;

		// 各辺の長さを取得
		double dist[3];
		dist[0] = m_vecPos[m_indices[i3]].distance(m_vecPos[m_indices[i3+1]]);
		dist[1] = m_vecPos[m_indices[i3+1]].distance(m_vecPos[m_indices[i3+2]]);
		dist[2] = m_vecPos[m_indices[i3]].distance(m_vecPos[m_indices[i3+2]]);
		
		// 三角形の中から最長の辺の長さを取得し
		// その1/2の値を矢印の軸の長さに用いる
		double max = dist[0];
		for(int j = 0; j < 2; j++){
			if(dist[j] < dist[j+1]){
				max = dist[j+1];
			}
		}
		double len = max/2;

		// 矢印の先端の座標を計算
		const MGVector& vecX = m_vecNormlTriang[i] * len;
		pos[1] = pos[0] + vecX;

		// 矢印の両端の座標を求める処理
		// 矢印の先端座標に加えるベクトルを計算
		const MGVector& head_rootx = vecX * .3;
		// 三角形の任意の辺のベクトルを取得し、面の法線べクトルとの積算を行う
		MGUnit_vector& arrowVec = (m_vecPos[m_indices[i3+1]]- m_vecPos[m_indices[i3]]).normalize();
		arrowVec *= m_vecNormlTriang[i];
		// 矢印の先端座標に加えるもう１つのベクトルを計算
		const MGVector& head_rooty = arrowVec*(.5*.3*vecX.len());
		// 矢印の両端の座標を計算
		pos[2]=pos[1]-head_rootx+head_rooty;
		pos[3]=pos[1]-head_rootx-head_rooty;

		// 矢印の描画を行う
		white.exec(sgl);
		sgl.drawArrow(pos);
	}
}

//Draw 3D curve in world coordinates.
//The object is converted to curve(s) and is drawn.
void MGFSurface::drawWireFS(
	mgVBO& vbo,
	int line_density	//line density to draw a surface in wire mode.
)const{
	//glPushAttrib(GL_LINE_BIT);
	mgLightModeSwitcher switcher(vbo, mgGLSL::NoShading);
	vbo.LineWidth(1.);//glLineWidth(1.);
	MGPvector<MGCurve> ilines=inner_skeleton(line_density);
	MGPvector<MGCurve>::const_iterator i=ilines.begin(), ie=ilines.end();
	for(; i!=ie; i++)
		(*i)->drawWire(vbo,line_density);

	vbo.LineWidth(2.);//glLineWidth(2.);
	MGPvector<MGCurve> bndries=get_all_boundaries();
	MGPvector<MGCurve>::const_iterator j=bndries.begin(), je=bndries.end();
	for(; j!=je; j++)
		(*j)->drawWire(vbo,line_density);
	//glPopAttrib();
}

//Draw 3D curve in world coordinates.
//The object is converted to curve(s) and is drawn.
void MGFSurface::drawWireFS_to_highlight(
	mgVBO& vbo,
	int line_density	//line density to draw a surface in wire mode.
)const{
	mgLightModeSwitcher switcher(vbo, mgGLSL::NoShading);
	MGPvector<MGCurve> ilines=skeleton(line_density);
	MGPvector<MGCurve>::const_iterator i=ilines.begin(), ie=ilines.end();
	for(; i!=ie; i++)
		(*i)->drawWire(vbo,line_density);
}

///Display arrows on the surface.
void MGFSurface::display_arrowsFS(mgSysGL& sgl,int udiv, int vdiv)const{

	MGPosition uv(2), pos[10];
	const MGBox& box = box_param2();
	double us = box[0].low_point(), ue = box[0].high_point(),
		vs = box[1].low_point(), ve = box[1].high_point();
	const MGColor& ucolor=MGColor::get_instance(MGColor::Red);
	const MGColor& vcolor=MGColor::get_instance(MGColor::Green);
	const MGColor& white=MGColor::get_instance(MGColor::White);
	for(int i = 0; i <= udiv; i++){
		uv(0) = (us*(udiv-i)+ue*i)/udiv;
		for(int j = 0; j <= vdiv; j++){
			uv(1) = (vs*(vdiv-j)+ve*j)/vdiv;
			arrow(uv, pos);

			ucolor.exec(sgl);
			sgl.drawArrow(&pos[0]);
			pos[3] = pos[0];
			
			vcolor.exec(sgl);
			sgl.drawArrow(&pos[3]);
			pos[6] = pos[0];
			
			white.exec(sgl);
			sgl.drawArrow(&pos[6]);
		}
	}
	//::glColor3f(0.0f, 0.0f, 0.0f);
}

///Shade the object in world coordinates.
void MGFSurface::shadeFS(
	mgVBO& vbo,
	const MGDrawParam& para,
	mgVBO::ELEMENT_TARGET target
)const{
	const MGObject* obj=object_pointer();
	obj->shade(vbo,para,target);
}

///Delete the mgVBO of the i-th element.
void MGGroup::delete_displayList(
	const_iterator x
)const{
	mgVBO* vbo=m_dlistName.get();
	if(vbo){
		const MGGel* geli=*x;
		const MGAttribedGel* ageli=dynamic_cast<const MGAttribedGel*>(geli);
		if(ageli)
			vbo->deleteGel(*ageli);
	}
}

///Delete display list of the sequence [first, last).
void MGGroup::delete_displayList(
	const_iterator first, const_iterator last
)const{
	mgVBO* vbo=m_dlistName.get();
	if(vbo){
		const_iterator i=first;
		for(; i!=last; i++){
			const MGGel* geli=*i;
			const MGAttribedGel* ageli=dynamic_cast<const MGAttribedGel*>(geli);
			if(ageli)
				vbo->deleteGel(*ageli);
		}
	}
}

///Delete the mgVBO of gels_to_delete
void MGGroup::delete_displayList(
	const std::vector<const MGGel*>& gels_to_delete
)const{
	mgVBO* vbo=m_dlistName.get();
	if(vbo){
		std::vector<const MGGel*>::const_iterator i,ie;
		i=gels_to_delete.begin();
		ie=gels_to_delete.end();

		for(; i!=ie; i++){
			const MGGel* geli=*i;
			const MGAttribedGel* ageli=dynamic_cast<const MGAttribedGel*>(geli);
			if(!ageli)
				continue;
			vbo->deleteGel(*ageli);
		}
	}
}

static const float edgeColor[4]={1.,.5,.5,0.};//Edge color to hilight.
static const float white[4]={1.,1.,1.,0.};//Highlight back color.
static const float endPointColor[4]={.5,1.,.5,0.};//Start/End point color to hilight.

///Highlightthe object using the display list of this object.
void MGPickObject::hilight_using_display_list(
	int line_density	///<line density to draw a surface in wire mode.
)const{
	mgVBO* nm=top_object()->dlist_name();
	nm->highlight();
}

///Highlight the object using the display list of this object.
void MGPickObjectSB::hilight_using_display_list(
	int line_density	///<line density to draw a surface in wire mode.
)const{
	MGPickObject::hilight_using_display_list(line_density);
	if(!m_vbo.is_made()){
		m_vbo.setStaticAttribColor(edgeColor);//glColor4fv(edgeColor);
		std::unique_ptr<MGCurve> e(surface()->perimeter_curve(perimeter()));
		e->drawWire(m_vbo);
	}
	m_vbo.highlight();
}

///Highlight the object using the display list of this object.
void MGPickObjectCB::hilight_using_display_list(
	int line_density	///<line density to draw a surface in wire mode.
)const{
	MGPickObject::hilight_using_display_list(line_density);
	const MGCurve* c=curve();
	MGPosition P=m_start_end ? c->end_point() : c->start_point();
	if(!m_vbo.is_made()){
		m_vbo.setStaticAttribColor(edgeColor);//glColor4fv(edgeColor);
		m_vbo.drawPoint(P);
	}
	m_vbo.highlight();
}

//////display member function.
#define NDIV 2
void MGCurve::display_arrows(mgSysGL& sgl)const{
	double ts=param_s(), te=param_e();
	MGPosition pos[4];
	for(int i = 0; i <= NDIV; i++){
		double param = (ts*(NDIV-i)+te*i)/NDIV;
		arrow(param, pos);
		sgl.drawArrow(pos);
	}
}
void MGCurve::display_break_points(mgSysGL& sgl)const{
	const MGKnotVector& t=knot_vector();
	double ts=param_s(), te=param_e();
	int k=t.order(), n=t.bdim();
	for(int i=k-1; i<=n; i++){
		double tau=t[i];
		if(i>=k && tau==t[i-1]) continue;
		if(tau<ts || tau>te) continue;
		MGVector P=eval(tau);
		sgl.drawPoint(P[0],P[1],P[2]);
	}
}
void MGLBRep::display_control_polygon(mgSysGL& sgl)const{
	const MGBPointSeq& bp=line_bcoef();
	sgl.drawPointSeq(bp);
}
void MGRLBRep::display_control_polygon(mgSysGL& sgl)const{
	MGBPointSeq bp = non_homogeneous_bcoef();
	sgl.drawPointSeq(bp);
}

void MGCurve::display_curvatures(
	mgSysGL& sgl,
	double	scale,	//scaling of the graph.
	int		density,//densitiy of the graph.
	bool	use_radius//true:radius display, false:curvature display.
)const{
	sgl.drawCurvaGraph(*this,scale,density,use_radius);
}

//////display member function.
void MGSurface::display_arrows(mgSysGL& sgl)const{
	display_arrowsFS(sgl);
}

//Display control polygons using mgVBO::MGDrawPointSeq(sp)
void MGSBRep::display_control_polygon(mgSysGL& sgl)const{
	const MGSPointSeq& sp=surface_bcoef();
	sgl.drawPointSeq(sp);
}

void MGRSBRep::display_control_polygon(mgSysGL& sgl)const{
	MGSPointSeq sp=non_homogeneous_bcoef();
	sgl.drawPointSeq(sp);
}

//////display member function.
void MGFace::display_arrows(mgSysGL& sgl)const{
	display_arrowsFS(sgl);
}
void MGFace::display_control_polygon(mgSysGL& sgl)const{
	sgl.setLineStipple(2,0x5555);
	surface()->display_control_polygon(sgl);
}

//////display member function.
void MGShell::display_arrows(mgSysGL& sgl)const{
	int n=number_of_faces();
	for(int i=0; i<n; i++)
		face(i)->display_arrows(sgl);
}
void MGShell::display_control_polygon(mgSysGL& sgl)const{
	sgl.setLineStipple(2,0x5555);
	int n=number_of_faces();
	for(int i=0; i<n; i++)
		face(i)->display_control_polygon(sgl);
}

MGDrawParam::MGDrawParam(
	const MGContext& contx,
	double span_length_wire
):m_span_length_wire(span_length_wire),
m_line_desity_wire_face(contx.line_density()),
m_maximum_edge_length_tess(contx.tess_maximum_edge_length()){
	build_crv_srf_tolerance(contx.tess_curve_tolerance(),contx.tess_surface_tolerance());
}

///Judge if the display list for vmode is made or not.
bool MGAttribedGel::displayList_is_made(MGCL::VIEWMODE vmode)const{
	mgVBO* vbo=m_dlistName.get();
	if(vbo)
		return vbo->is_made(vmode);
	return false;
}

///Judge if the display list for vmode is made or not.
/*bool MGGroup::displayList_is_made(MGCL::VIEWMODE vmode)const{
	if(includes_object())
		return MGAttribedGel::displayList_is_made(vmode);

	mgVBO* vbo=dlist_name();
	if(!vbo->staticColor().defined()){
		int mask=get_draw_attrib_mask();
		if(mask)
			return false;
	}
	return true;
}*/

///Get the number of shading elements of m_dlistName.
int MGAttribedGel::getVBOElementsNumber()const{
	mgVBO* vbo=dlist_name();
	if(!vbo)
		return 0;
	return (int)vbo->m_elements.size();
}

///Get the number of shading elements of m_dlistName.
int MGAttribedGel::getVBOShaderElementsNumber()const{
	mgVBO* vbo=dlist_name();
	if(!vbo)
		return 0;
	return (int)vbo->m_elementsShade.size();
}

//Shade the object in world coordinates.
void MGSurface::shade(
	mgVBO& vbo,
	const MGDrawParam& para,
	mgVBO::ELEMENT_TARGET target
)const{
	mgTL2Face face(para,*this);
	mgTL2Triangles tris(MGCL::XYZNormal,this);
	face.tessellate(tris);	
	vbo.drawShade(tris,target);
}

///Triangulate this object(MGShell, MGFace, or MGSurface is the target).
void MGSurface::triangulate(
	const MGDrawParam& para,
	MGCL::TL_DATA_KIND dkind,
	std::vector<mgTL2Triangles>& trisVec
)const{
	mgTL2Face face(para,*this);
	mgTL2Triangles tris(dkind,this);
	face.tessellate(tris);	
	trisVec.push_back(tris);
}

///Set no display for this vector of MGPickObject.
void MGPickObjects::setNoDisplay()const{
	const_iterator i=begin(), ie=end();
	for(; i!=ie; i++){
		const MGPickObject& pobji=**i;
		const MGObject* obji=pobji.top_object();
		mgVBO* vbo=obji->dlist_name();
		if(vbo)
			vbo->set_no_display();
	}
}

///Set no display for this vector of MGPickObject.
void MGPickObjects::setDisplay()const{
	const_iterator i=begin(), ie=end();
	for(; i!=ie; i++){
		const MGPickObject& pobji=**i;
		const MGObject* obji=pobji.top_object();
		mgVBO* vbo=obji->dlist_name();
		if(vbo)
			vbo->set_display();
	}
}
