/********************************************************************/
/* Copyright (c) 2019 System fugen G.K. and Yuzi Mizuno          */
/* All rights reserved.                                             */
/* ***************************************************** */
/********************************************************************/
// mgcalc_line.cpp
#include "stdafx.h"
#include "Calc/curve.h"
#include "Calc/line.h"
#include "mg/LBRep.h"
#include "mg/Plane.h"
#include "mg/Straight.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

namespace mgcalc{
	// 線分を生成する
	std::unique_ptr<MGCurve> create_line(
		const MGPosition& ref,
		const MGPosition& end,
		bool              bothside
	){
		// 単純にコンストラクタを呼ぶだけ
		return std::unique_ptr<MGCurve>(new MGStraight(end,bothside ? (2*ref - end):ref));
	}

	// 曲線の端点における直線的延長部分を生成する
	std::unique_ptr<MGCurve> create_line_by_extension(
		const MGCurve&    curve,
		double            param,
		const MGPosition& hint,
		bool              bothside
		){
		if(!curve.in_range(param)){
			return std::unique_ptr<MGCurve>(nullptr);
		}
		// パラメータを始点か終点の近いほうに変更する
		param = curve.param_se(param);
		assert(param == curve.param_s() || param == curve.param_e());

		// 接線方向
		MGVector dir = curve.eval(param, 1);
		if(dir.is_zero_vector()){
			return std::unique_ptr<MGCurve>(nullptr);
		}
		// 端点は始点か中点になる
		const MGVector ref = curve.eval(param);
		MGVector tmp = hint - ref;

		// 終点を計算
		tmp = ref + tmp.project(dir);

		return create_line(ref, tmp, bothside);
	}


	// 曲線の垂線を生成する
	std::unique_ptr<MGCurve> create_perp_line(
		const MGCurve&    curve,
		const MGPosition& pos,
		bool              bothside
		){
		double param;
		int ret = curve.perp_point(pos, param);
		if(ret == 0){
			return std::unique_ptr<MGCurve>(nullptr);
		}
		const MGPosition& end = curve.eval(param);
		if(end == pos){
			return std::unique_ptr<MGCurve>(nullptr);
		}
		// 線分生成関数に任せる
		return create_line(pos, end, bothside);
	}

	// 曲線の垂線と垂線の足における接線を返す
	std::pair<MGCurve*, MGCurve*> create_perp_line_ex(
		const MGCurve&    curve,
		const MGPosition& end,
		bool              bothside
		){
		double param;
		int ret = curve.perp_point(end, param);
		if(ret == 0){
			return std::pair<MGCurve*, MGCurve*>();
		}
		const MGPosition& ref = curve.eval(param);
		if(ref == end){
			return std::pair<MGCurve*, MGCurve*>();
		}
		
		// 線分を生成
		
		std::unique_ptr<MGCurve> perp(create_line(ref, end, bothside));
		if(!perp.get()){
			return std::pair<MGCurve*, MGCurve*>();
		}
		std::unique_ptr<MGCurve> tan(create_tangent_line(curve, param));
		if(!tan.get()){
			return std::pair<MGCurve*, MGCurve*>();
		}
		return std::make_pair(perp.release(), tan.release());
	}

	// 円に外接する正 N 角形を生成
	std::unique_ptr<MGCurve> create_polygon_cir(
		const MGPosition& center,
		const MGVector&   normal,
		int               N,
		const MGPosition& mid
		){
		assert(center.sdim() == 3);
		assert(normal.sdim() == 3);
		assert(mid.sdim() == 3);

		if(center == mid || normal.is_zero_vector() || N < 3){
			return std::unique_ptr<MGCurve>(nullptr);
		}

		// 最初の頂点を計算する
		double mgPAI_P_N = mgPAI/N;
		MGPosition v0 = center + ((mid - center) / cos(mgPAI_P_N));
		MGTransf rot;
		rot.set_rotate_3D(normal, mgPAI_P_N, center);
		v0 *= rot;

		// ins 使用
		return create_polygon_ins(center, normal, N, v0);
	}

	// ひとつの辺を指定して正 N 角形を生成
	std::unique_ptr<MGCurve> create_polygon_edge(
		const MGStraight& edge0,
		const MGVector&   normal,
		int               N
		){
		assert(edge0.sdim() == 3);
		assert(normal.sdim() == 3);
		if(edge0.type() != MGSTRAIGHT_SEGMENT || normal.is_zero_vector() || N < 3){
			return std::unique_ptr<MGCurve>(nullptr);
		}
		if(edge0.start_point() == edge0.end_point()){
			return std::unique_ptr<MGCurve>(nullptr);
		}
		
		// 回転を計算
		MGTransf rot;
		rot.set_rotate_3D(normal, mgDBLPAI/N);

		// 多角形の頂点をセット
		std::vector<MGPosition> c;
		c.reserve(N + 1);

		// 細かく向きを変えながら一周
		MGStraight e(edge0);
		MGPosition sav;
		for(int i = 0; i < N; i++){
			c.push_back(e.start_point());
			sav = e.end_point();
			e -= e.start_point();
			e *= rot;
			e += sav;
		}
		c.push_back(c.front());

		// create_polyline() を流用
		return create_polyline(c);
	}

	// 円に内接する正 N 角形を生成
	std::unique_ptr<MGLBRep> create_polygon_ins(
		const MGPosition& center,
		const MGVector&   normal,
		int               N,
		const MGPosition& V0
		){
		assert(center.sdim() == 3);
		assert(normal.sdim() == 3);
		assert(V0.sdim() == 3);

		if(center == V0 || normal.is_zero_vector() || N < 3){
			return std::unique_ptr<MGLBRep>(nullptr);
		}

		// 単位回転量をセット
		MGTransf rot;
		rot.set_rotate_3D(normal, mgDBLPAI / N, center);

		// 多角形の頂点をセット
		std::vector<MGPosition> c;
		c.reserve(N + 1);

		MGPosition vertex(V0);
		for(int i = 0; i < N; i++){
			c.push_back(vertex);
			vertex *= rot;
		}
		c.push_back(c.front());

		// create_polyline() を流用
		return create_polyline(c);
	}

	// 星型多角形を生成する
	std::unique_ptr<MGLBRep> create_polygon_star(
		const MGPosition& center,
		const MGVector&   normal,
		int               N,
		const MGPosition& V0,
		double            radius2nd
		){
		assert(center.sdim() == 3);
		assert(normal.sdim() == 3);
		assert(V0.sdim() == 3);
		if(center == V0 || normal.is_zero_vector() || N < 3 || radius2nd == 0.){
			return std::unique_ptr<MGLBRep>(nullptr);
		}

		// 単位回転量をセット
		MGTransf rot;
		rot.set_rotate_3D(normal, mgDBLPAI / N, center);

		MGPosition vertex1st(V0);
		
		// 2nd 側円周上の点列の最初の点を計算
		MGTransf rot2;
		rot2.set_rotate_3D(normal, mgPAI / N, center);
		MGPosition vertex2nd(V0);
		vertex2nd *= rot2;
		vertex2nd -= center;
		vertex2nd *= (radius2nd / V0.distance(center));
		vertex2nd += center;

		// 多角形の頂点をセット
		std::vector<MGPosition> c;
		N *= 2;
		c.reserve(N + 1);
		for(int i = 0; i < N; i++){
			if(i % 2 == 0){
				// 1st 側
				c.push_back(vertex1st);
				vertex1st *= rot;
			}
			else{
				// 2nd 側
				c.push_back(vertex2nd);
				vertex2nd *= rot;
			}
		}
		c.push_back(c.front());

		// create_polyline() を流用
		return create_polyline(c);
	}

	// ポリライン(折れ線)を生成
	std::unique_ptr<MGLBRep> create_polyline(const std::vector<MGPosition>& pos){
		std::unique_ptr<MGLBRep> polyline;
		MGBPointSeq bp(pos);
		if(bp.length()>=2){
			polyline.reset(new MGLBRep);
			polyline->buildByInterpolation(bp, 2);
		}
		return polyline;
	}
		
	/**
	 *  @brief same as create_polyline except that create_closed_polyline adds the
	 *  additional line segment from pos[n-1] to pos[0].
	*/
	std::unique_ptr<MGLBRep> create_closed_polyline(const std::vector<MGPosition>& pos){
		std::unique_ptr<MGLBRep> polyline;
		MGBPointSeq bp(pos);
		int n=bp.length();
		if(n>=3){
			bp.reshape(n+1);
			bp.store_at(n,pos[0]);			
			polyline.reset(new MGLBRep);
			polyline->buildByInterpolation(bp, 2);
		}
		return polyline;
	}

	namespace{
		const double pseudo_infinity = 5e4;
	}

	// 曲線上の接線を無限直線として生成する
	std::unique_ptr<MGCurve> create_tangent_line(
		const MGCurve& curve,
		double         param
		){
		if(!curve.in_range(param)){
			return std::unique_ptr<MGCurve>(nullptr);
		}

		MGUnit_vector T, N, B;
		double curva, tor;
		curve.Frenet_frame(param, T, N, B, curva, tor);

		// T 方向へ伸ばす
		std::unique_ptr<MGCurve> line(new MGStraight(MGSTRAIGHT_UNLIMIT, T, curve.eval(param)));
		line->limit(MGInterval(-pseudo_infinity, pseudo_infinity));
		return line;
	}

	// 曲線上の接線を、終点に近い点を指定して生成
	std::unique_ptr<MGCurve> create_tangent_line(
		const MGCurve&    curve,
		double            param,
		const MGPosition& hint,
		bool              bothside
		){
		if(!curve.in_range(param)){
			return std::unique_ptr<MGCurve>(nullptr);
		}
		// 接線方向取得
		MGVector T, N, B;
		double curva, tor;
		curve.Frenet_frame(param, T, N, B, curva, tor);
		
		const MGPosition& pos = curve.eval(param);
		MGVector tmp = hint - pos;
		if(tmp.is_zero_vector()){
			return std::unique_ptr<MGCurve>(nullptr);
		}
		tmp = pos + tmp.project(T);
		return create_line(pos, tmp, bothside);
	}

} // namespace mgcalc
