/*
								+----------------------------------+
								|                                  |
								|     ***   Bezier cubic   ***     |
								|                                  |
								|   Copyright © -tHE SWINe- 2008   |
								|                                  |
								|          BezierCubic.h           |
								|                                  |
								+----------------------------------+
*/

#pragma once
#ifndef __BEZIER_CUBIC_SPLINE_INCLUDED
#define __BEZIER_CUBIC_SPLINE_INCLUDED

/**
 *	@file lml/BezierCubic.h
 *	@date 2008
 *	@author -tHE SWINe-
 *	@brief Bezier cubic
 *
 *	@date 2008-08-21
 *
 *	CBezierCubicSpline is now template parametrized by point type, default is Vector3f
 *
 *	@date 2012-06-19
 *
 *	Moved multiple inclusion guard before file documentation comment.
 *
 *	@date 2014-03-18
 *
 *	Made difference between the key points the spline is defined on and the domain points
 *	the spline outputs (for e.g. Bezier splines those are the same, for rational bezier or
 *	e.g. Kochanek-Bartels, key-points are positions + parameters (weights, continuity, ...),
 *	domain points are only positions).
 *
 */

#include "Spline.h"

/**
 *	@brief simple Bezier cubic spline implementation
 */
template <class CPointClass = Vector3f>
class CBezierCubicSplineImpl {
public:
	typedef CPointClass _TyPoint; /**< @brief type of points the spline is defined on */
	typedef CPointClass _TyKeyPoint; /**< @brief type of spline kaypoints */
	typedef typename CPointClass::_TyScalar _TyScalar; /**< @brief type of scalar */

	/**
	 *	@brief spline traits, stored as enum
	 */
	enum {
		b_has_explicit_time = false, /**< @brief the spline keypoints do not have time associated with them */
		b_has_rotation = false /**< @brief the spline keypoints do not have t_rotation member */
	};

protected:
	std::vector<_TyKeyPoint> m_point_list;

public:
	/**
	 *	@brief gets number of spline arcs
	 *	@return Returns number of spline arcs.
	 */
	inline size_t n_Arc_Num() const
	{
		return (max(m_point_list.size(), size_t(1)) - 1) / 3;
	}

	/**
	 *	@brief calculates position on a spline arc
	 *
	 *	@param[in] f_t is position on a spline arc in range [0, 1]
	 *	@param[in] n_arc is zero-based index of spline arc
	 *
	 *	@return Returns point at the given position along the selected arc.
	 */
	CPointClass v_Arc_Position(_TyScalar f_t, size_t n_arc) const
	{
		_ASSERTE(n_arc < n_Arc_Num());

		n_arc *= 3;
		CPointClass v_pt0 = m_point_list[n_arc];
		CPointClass v_pt1 = m_point_list[n_arc + 1];
		CPointClass v_pt2 = m_point_list[n_arc + 2];
		CPointClass v_pt3 = m_point_list[n_arc + 3];
		// get polygon points

		_TyScalar f_omt = 1 - f_t;
		_TyScalar f_omt2 = f_omt * f_omt;
		_TyScalar f_omt3 = f_omt2 * f_omt;
		_TyScalar f_t2 = f_t * f_t;
		_TyScalar f_t3 = f_t2 * f_t;
		// calculate 1-t, (1-t)^2, (1-t)^3, t^2, t^3

		_TyScalar f_weight0 = f_omt3;
		_TyScalar f_weight1 = 3 * f_t * f_omt2;
		_TyScalar f_weight2 = 3 * f_t2 * f_omt;
		_TyScalar f_weight3 = f_t3;
		// calculate Bernstein polynoms

		return v_pt0 * f_weight0 + v_pt1 * f_weight1 +
			v_pt2 * f_weight2 + v_pt3 * f_weight3;
		// interpolate
	}

	/**
	 *	@brief calculates tangent vector on a spline arc
	 *
	 *	@param[in] f_t is position on a spline arc in range [0, 1]
	 *	@param[in] n_arc is zero-based index of spline arc
	 *
	 *	@return Returns tangent at the given position along the selected arc.
	 */
	CPointClass v_Arc_Derivative(_TyScalar f_t, size_t n_arc) const
	{
		_ASSERTE(n_arc < n_Arc_Num());

		n_arc *= 3;
		CPointClass v_pt0 = m_point_list[n_arc];
		CPointClass v_pt1 = m_point_list[n_arc + 1];
		CPointClass v_pt2 = m_point_list[n_arc + 2];
		CPointClass v_pt3 = m_point_list[n_arc + 3];
		// get polygon points

		_TyScalar f_omt = 1 - f_t;
		_TyScalar f_omt2 = f_omt * f_omt;
		_TyScalar f_omt3 = f_omt2 * f_omt;
		_TyScalar f_t2 = f_t * f_t;
		_TyScalar f_t3 = f_t2 * f_t;
		// calculate 1-t, (1-t)^2, (1-t)^3, t^2, t^3

		_TyScalar f_t_ = 1;
		_TyScalar f_omt_ = -f_t_;
		_TyScalar f_omt2_ = f_omt * f_omt_ + f_omt_ * f_omt;
		_TyScalar f_omt3_ = f_omt2 * f_omt_ + f_omt2_ * f_omt;
		_TyScalar f_t2_ = f_t * f_t_ + f_t_ * f_t;
		_TyScalar f_t3_ = f_t2 * f_t_ + f_t2_ * f_t;
		// calculate derivatives of 1-t, (1-t)^2, (1-t)^3, t^2, t^3

		_TyScalar f_weight0 = f_omt3_;
		_TyScalar f_weight1 = 3 * (f_t_ * f_omt2 + f_t * f_omt2_);
		_TyScalar f_weight2 = 3 * (f_t2_ * f_omt + f_t2 * f_omt_);
		_TyScalar f_weight3 = f_t3_;
		// calculate derivatives of Bernstein polynoms

		return v_pt0 * f_weight0 + v_pt1 * f_weight1 +
			v_pt2 * f_weight2 + v_pt3 * f_weight3;
		// interpolate
	}
};

/**
 *	@brief simple Bezier cubic spline template, parametrized by point type
 */
template <class CPointClass>
class CBezierCubicSpline : public CSplineIface<CBezierCubicSplineImpl<CPointClass> > {
public:
	/**
	 *	@copydoc CSplineBase::CSplineBase()
	 */
	inline CBezierCubicSpline()
		:CSplineIface()
	{}

	/**
	 *	@copydoc CSplineBase::CSplineBase(const std::vector<_TyKeyPoint>&)
	 */
	inline CBezierCubicSpline(const std::vector<_TyKeyPoint> &r_point_list) // throw(std::bad_alloc)
		:CSplineIface(r_point_list)
	{}

	/**
	 *	@copydoc CSplineBase::CSplineBase(CPointIter,CPointIter)
	 */
	template <class CPointIter>
	inline CBezierCubicSpline(CPointIter p_point_begin, CPointIter p_point_end) // throw(std::bad_alloc)
		:CSplineIface(p_point_begin, p_point_end)
	{}

	/**
	 *	@copydoc CSplineBase::CSplineBase(const CSplineBase&)
	 */
	inline CBezierCubicSpline(const CBezierCubicSpline &r_other) // throw(std::bad_alloc)
		:CSplineIface(r_other)
	{}

	/**
	 *	@copydoc CSplineBase::operator=()
	 */
	inline CBezierCubicSpline &operator =(const CBezierCubicSpline &r_other) // throw(std::bad_alloc)
	{
		*(CSplineIface*)this = r_other;
		return *this;
	}
};

#endif // !__BEZIER_CUBIC_SPLINE_INCLUDED
