/********************************************************************/
/* Copyright (c) 2019 System fugen G.K. and Yuzi Mizuno             */
/* All rights reserved.                                             */
/* **************************************************************** */

// conic.cpp
#include "stdafx.h"
#include "Calc/conic.h"
#include "mg/CCisects.h"
#include "mg/RLBRep.h"
#include "mg/Straight.h"

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

namespace mgcalc{
	// 円錐曲線を生成する
	// rho 指定バージョン
	std::unique_ptr<MGCurve> create_conic_curve(
		const MGPosition& start, //円錐曲線の始点
		const MGPosition& end,   //円錐曲線の終点
		const MGPosition& apex,  //始点における接線と終点における接線の交点
		double            rho    //w/(1+w), where w is the weight of control point ref.
		){
		assert(!start.is_null());
		assert(!end.is_null());
		assert(!apex.is_null());
		if(rho < 0 || rho > 1 || apex.is_collinear(start, end)){
			return std::unique_ptr<MGCurve>(nullptr);
		}
		// 1. 制御点
		MGBPointSeq bp(3, 4); // (size, sdim)
		bp.store_at(0, start);
		bp.store_at(1, apex);
		bp.store_at(2, end);
		bp(0, 3) = 1;
		bp(1, 3) = rho / (1 - rho); // weight
		bp(2, 3) = 1;

		// 2. ノットベクトル
		MGKnotVector t(3, 3, 0., 1.); // {0,0,0,1,1,1}

		// 3. 曲線を生成
		std::unique_ptr<MGCurve> conic(new MGRLBRep(t, bp, 0));
		return conic;
	}

	// 円錐曲線を生成する
	// 通過点指定バージョン
	std::unique_ptr<MGCurve> create_conic_curve(
		const MGPosition& start,
		const MGPosition& end,
		const MGPosition& apex,
		const MGPosition& through
		){
		assert(!start.is_null());
		assert(!end.is_null());
		assert(!apex.is_null());
		if(apex.is_collinear(start, end)){
			return std::unique_ptr<MGCurve>(nullptr);
		}
		// 1. weight を計算
		// *** 間違っている可能性大 ***
		MGStraight chord(end, start);
		MGStraight line(through, apex);
		line.unlimit_end();

		MGCCisects ls = line.isect(chord);
		if(ls.size() != 1){
			return std::unique_ptr<MGCurve>(nullptr);
		}
		const MGCCisect& is = isectCast<const MGCCisect>(ls.begin());
		const MGPosition& pos = is.point();
		double AP = through.distance(apex);
		double PT = is.point().distance(through);
		double AT = apex.distance(is.point());
		if(AP + PT > AT || MGAZero(AT)){
			return std::unique_ptr<MGCurve>(nullptr);
		}

		AP /= AT; PT /= AT;
		double weight = PT / (1 - PT);
		

		// 2. 制御点を計算
		MGBPointSeq bp(3, 4); // (size, sdim)
		bp.store_at(0, start);
		bp.store_at(1, apex);
		bp.store_at(2, end);
		bp(0, 3) = 1;
		bp(1, 3) = weight;
		bp(2, 3) = 1;

		// 3. ノットベクトル
		MGKnotVector t(3, 3, 0., 1.); // {0,0,0,1,1,1}

		// 4. 曲線を生成して終了
		std::unique_ptr<MGCurve> conic(new MGRLBRep(t, bp, 0));
		return conic;
	}

	namespace{
		// 放物線生成関数の共通部分
		std::unique_ptr<MGCurve> create_parabola_aux(
			const MGVector& Q0,
			const MGVector& Q1,
			const MGVector& Q2,
			bool            halfside
			){
			// 1. construction
			MGBPointSeq bp(3, 3);
			bp.store_at(0, Q0);
			bp.store_at(1, Q1);
			bp.store_at(2, Q2);
			
			MGKnotVector t(3, 3, 0., 1.); // {0,0,0,1,1,1}
			std::unique_ptr<MGLBRep> parabola(new MGLBRep);
			parabola->buildLBRepFromMemberData(std::move(t), std::move(bp));
			
			// 2. 片側を削るかどうか
			if(halfside){
				parabola->limit(MGInterval(0.5, 1.0));
			}
			return parabola;
		}
	}

	// 頂点と方向と終点を与えて放物線を生成する
	std::unique_ptr<MGCurve> create_parabola(
		const MGPosition& vertex,
		const MGVector&   direction,
		const MGPosition& end,
		bool              halfside
		){
		assert(vertex.sdim() == 3);
		assert(direction.sdim() == 3);
		assert(end.sdim() == 3);
		
		// direction が 零ベクトルのとき失敗する。
		if(direction.is_zero_vector()){
			return std::unique_ptr<MGCurve>(nullptr);
		}
		// end が vertex の direcion 方向上にあるとき失敗する。
		if((end - vertex).parallel(direction)){
			return std::unique_ptr<MGCurve>(nullptr);
		}
		// 制御点の算出
		// 1. end の軸に関する対象点 end2 を求める
		MGVector vertex_to_end = end - vertex;
		MGVector axis = vertex_to_end.project(direction);
		MGPosition Q0 = vertex + 2*axis - vertex_to_end;
		
		if(!(Q0 - end).orthogonal(axis)){
			return std::unique_ptr<MGCurve>(nullptr);
		}

		// 2. 放物線の制御点のうち、対称軸上にあるものを求める
		const MGPosition Q1 = vertex - axis;

		// 3. MGLBRep オブジェクトを作成
		return create_parabola_aux(Q0, Q1, end, halfside);
	}

	// 頂点と焦点とマウスカーソルを与えて放物線を生成
	//
	// vertex と focus が一致しているとき失敗する
	// cursor が 放物線の軸上にあるとき失敗する
	std::unique_ptr<MGCurve> create_parabola(
		const MGPosition& vertex,
		const MGPosition& focus,
		const MGPosition& cursor,
		bool              halfside
		){
		assert(vertex.sdim() == 3);
		assert(focus.sdim() == 3);
		assert(cursor.sdim() == 3);

		// 1. 引数チェック
		// vertex と focus が一致しているとき失敗する
		if(vertex == focus){
			return std::unique_ptr<MGCurve>(nullptr);
		}

		// cursor が 放物線の軸上にあるとき失敗する
		MGVector vertex_to_focus = focus - vertex;
		MGVector vertex_to_cursor = cursor - vertex;
		if((vertex_to_cursor).parallel(vertex_to_focus)){
			return std::unique_ptr<MGCurve>(nullptr);
		}

		// 2. 制御点計算
		// 最適化は諦めたわけではないが...
		const MGVector Q1 = vertex + vertex - focus;
		const MGVector Q1C = cursor - Q1;
		const MGVector VF  = focus - vertex;
		const MGVector y = Q1C.project(VF);
		const MGVector x = Q1C - y;
		const MGVector Q2 = focus + Q1C - y;
		const MGVector Q0 = Q2 - x - x;

		// 3. MGLBRep オブジェクトを作成
		return create_parabola_aux(Q0, Q1, Q2, halfside);
	}
	
} // namespace mgcalc
