/*
								+----------------------------------+
								|                                  |
								| ***  Poisson disc generator  *** |
								|                                  |
								|   Copyright © -tHE SWINe- 2007   |
								|                                  |
								|            Poisson.h             |
								|                                  |
								+----------------------------------+
*/

#pragma once
#ifndef __POISSON_GENERATOR_INCLUDED
#define __POISSON_GENERATOR_INCLUDED

/**
 *	@file Poisson.h
 *	@brief Poisson disc generator
 *	@date 2007
 *	@author -tHE SWINe-
 */

#include "../Integer.h"
#include <vector>
#include "../Vector.h"
#include "../RandGen.h"

/**
 *	@brief simple numeric interval structure
 */
template <class CScalar>
struct TInterval {
	typedef CScalar _TyScalar; /**< @brief scalar type */

	_TyScalar f_min; /**< @brief lower bound */
	_TyScalar f_max; /**< @brief upper bound */

	/**
	 *	@brief default constructor, parameters are interval end points
	 *
	 *	@param[in] _f_min is lower bound
	 *	@param[in] _f_max is upper bound
	 */
	inline TInterval(_TyScalar _f_min = 0, _TyScalar _f_max = 2 * f_pi)
		:f_min(_f_min), f_max(_f_max)
	{
		_ASSERTE(_f_min <= _f_max);
	}

	/**
	 *	@brief empty interval predicate
	 *	@return Returns true in case interval is empty, otherwise false.
	 */
	inline bool b_Empty() const
	{
		_ASSERTE(f_min <= f_max);
		return f_min >= f_max;
	}

	/**
	 *	@brief disjunct interval predicate
	 *	@param[in] r_interval is other interval
	 *	@return Returns true in case this interval and r_interval are disjunct, otherwise false.
	 */
	inline bool b_Disjunct(const TInterval &r_interval) const
	{
		_ASSERTE(f_min <= f_max);
		_ASSERTE(r_interval.f_min <= r_interval.f_max);
		return f_max < r_interval.f_min || f_min > r_interval.f_max;
	}

	/**
	 *	@brief left complement of two intervals
	 *	@return Returns the left part of r_interval's complement in this interval (this - r_interval).
	 *	@note This returns an empty interval if this interval
	 *		is contained in r_interval is on the left of this.
	 */
	inline TInterval t_LeftComplement(const TInterval &r_interval) const
	{
		_ASSERTE(f_min <= f_max);
		_ASSERTE(r_interval.f_min <= r_interval.f_max);
		_ASSERTE(f_min <= r_interval.f_min);
		return TInterval(f_min, (f_max < r_interval.f_min)? f_max : r_interval.f_min);
	}

	/**
	 *	@brief right complement of two intervals
	 *	@return Returns the right part of r_interval's complement in this interval (this - r_interval).
	 *	@note This returns an empty interval if this interval
	 *		is contained in r_interval is on the right of this.
	 */
	inline TInterval t_RightComplement(const TInterval &r_interval) const
	{
		_ASSERTE(f_min <= f_max);
		_ASSERTE(r_interval.f_min <= r_interval.f_max);
		_ASSERTE(f_max >= r_interval.f_max);
		return TInterval((f_min > r_interval.f_max)? f_min : r_interval.f_max, f_max);
	}
};

/**
 *	@brief disc structure; contains center position, radius and free boundary arc
 */
struct TDisc {
	/**
	 *	@brief constants, stored as enum
	 */
	enum {
		n_max_interval_num = 6 /**< @brief there can be up to 6 discs on radius of another disc */
	};

	TInterval<float> p_arc_angle[n_max_interval_num]; /**< @brief free arc intervals */
	int n_interval_num; /**< @brief number of non-empty intervals */
	float f_radius; /**< @brief radius */
	Vector2f v_center; /**< @brief center */

	/**
	 *	@brief default constructor; has no effect
	 */
	inline TDisc()
	{}

	/**
	 *	@brief constructor; creates disc with a specified center and radius 
	 *
	 *	@apram[in] r_v_center is disc center
	 *	@apram[in] _f_radius is disc radius
	 */
	TDisc(const Vector2f &r_v_center, float _f_radius);

	/**
	 *	@brief conversion to 2D point
	 *	@return Returns disc center.
	 */
	inline operator Vector2f() const
	{
		return v_center;
	}

	/**
	 *	@brief subtracts area of other disc from arc of this disc
	 *	@param[in] r_t_disc is the other disc
	 *	@return Returns true on disc collision, false on no collision.
	 */
	bool Subtract(const TDisc &r_t_disc);

	/**
	 *	@brief substracts angular interval from this disc's arc
	 *
	 *	@param[in] f_angle0 is starting angle, in the [0, 2 * pi] interval
	 *	@param[in] f_angle1 is end angle, in [f_angle0, f_angle0 + 2 * pi] interval
	 *
	 *	@note The disc structure is able to describe just one continuous interval,
	 *		in case more intervals originate by subtraction, the longest one is stored.
	 */
	void SubtractAngleInterval(float f_angle0, float f_angle1);

	/**
	 *	@brief calculates angular length of the free arc
	 *	@return Returns angular length of the free arc (in radians).
	 */
	float f_FreeArc_Length() const;

	/**
	 *	@brief gets a point on free arc
	 *	@param[in] f_angle is angle along the free arc, in radians
	 *	@return Returns returns point lying at free arc at the specified position
	 *		(meassured on free arc segments only), or in case there is less
	 *		than f_angle free arc, returns center point to mark error.
	 */
	Vector2f v_FreeArc_Point(float f_angle) const;
};

/**
 *	@brief repeatable Poisson-disc set
 */
class CPoissonDisc_Set {
protected:
	std::vector<TDisc> m_disc_list;
	bool m_b_status;

public:
	/**
	 *	@brief default constructor; generates a pseudo-random Poisson-disc set
	 *
	 *	This fills an unit square with discs of the given radius (the square lies in the first
	 *	quadrant and one of its corners is identical with the origin).
	 *
	 *	@param[in] f_disc_radius is disc radius
	 *	@param[in] b_repeatable is repeatable flag (set if the points are
	 *		required to tile along x and y axis)
	 *	@param[in] r_generator is pseudorandom number generator
	 *
	 *	@note Call b_Status() to determine wheter constructor succeeded or not.
	 *	@note This is slow for big sets because there's no spatial
	 *		subdivision structure employed to accelerate.
	 */
	CPoissonDisc_Set(float f_disc_radius, bool b_repeatable, CRandomGeneratorModel<> &r_generator);

	/*
	 *	bool CRepeatable_PDSet::b_Status() const
	 *		- if constructor succeeded returns true, otherwise returns false
	 */
	inline bool b_Status() const
	{
		return m_b_status;
	}

	/**
	 *	@brief gets the generated disc list
	 *	@return Returns const reference to the generated disc list.
	 */
	inline const std::vector<TDisc> &r_DiscList() const
	{
		return m_disc_list;
	}
};

/**
 *	@brief poisson disc repeatable sample pattern for supersampling
 */
class CSamplePattern {
protected:
	std::vector<Vector2f> m_sample_list; /**< @brief list of sample positions */

public:
	/**
	 *	@brief default constructor; generates a poisson disc sample pattern
	 *
	 *	This generates pseudo-random sample with a desired number of points,
	 *	all of the points lie inside a (0, 0) - (1, 1) square and can be tiled
	 *	along both axes, if b_repeatable is set.
	 *
	 *	@param[in] n_point_num is desired number of samples
	 *	@param[in] n_point_num_tolerance is allowed tolerance for sample count (0 will do for counts below 10000)
	 *	@param[in] b_repeatable is repeatable flag (set if the points are
	 *		required to tile along x and y axis)
	 *	@param[in] r_generator is pseudo-random number generator
	 *	@param[in] n_seed is desired random number seed (might have to restart the process in some cases)
	 *
	 *	@note In case the constructor fails, the number of generated points is zero.
	 */
	CSamplePattern(size_t n_point_num, size_t n_point_num_tolerance,
		bool b_repeatable, CRandomGeneratorModel<> &r_generator, int n_seed);

	/**
	 *	@brief gets the number of sample points
	 *	@return Returns the number of sample points.
	 *	@note If the constructor failed this will return zero.
	 */
	inline size_t n_Sample_Num() const
	{
		return m_sample_list.size();
	}

	/**
	 *	@brief gets a single sample point
	 *	@param[in] n_index is zero-based sample index
	 *	@return Returns sample point with the specified index.
	 */
	inline const Vector2f &v_Sample(int n_index) const
	{
		return m_sample_list[n_index];
	}

	/**
	 *	@brief gets the list of generated samples
	 *	@return Returns const reference to the sample list.
	 */
	const std::vector<Vector2f> &r_SampleList() const
	{
		return m_sample_list;
	}

	/**
	 *	@brief shifts center of bounding box of all samples to origin
	 *	@note The bounding box is used instead of averaged samples position because
	 *		the samples are evenly distributed and so the difference should be minimal.
	 */
	void Center();

	/**
	 *	@brief multiplies all sample positions by a given factor
	 *	@param[in] f_scale is scale factor
	 */
	void Scale(float f_scale);

protected:
	bool GenerateDiscs(size_t n_point_num, size_t n_point_num_tolerance,
		bool b_repeatable, CRandomGeneratorModel<> &r_generator, int n_seed);
};

#endif //__POISSON_GENERATOR_INCLUDED
