/*
								+----------------------------------+
								|                                  |
								| *** Generic GPU particle sys *** |
								|                                  |
								|   Copyright © -tHE SWINe- 2008   |
								|                                  |
								|         GPUParticles.cpp         |
								|                                  |
								+----------------------------------+
*/

/**
 *	@file dev/GPUParticles.cpp
 *	@date 2008
 *	@author -tHE SWINe-
 *	@brief generic GPU particle systems
 *
 *	@date 2008-08-08
 *
 *	added \#ifdef for windows 64, added \#define for GL_GLEXT_LEGACY (for linux builds)
 *
 *	@date 2009-05-23
 *
 *	removed all instances of std::vector::reserve and replaced them by stl_ut::Reserve_*
 *
 */

#include "../NewFix.h"

#include "../CallStack.h"
#include <vector>
#include <algorithm>
#include "../OpenGL20.h"
#include "../OpenGLState.h"
#include "../VertexBufferObject.h"
#include "../Texture.h"
#include "../dev/GPUParticles.h"
#include "../StlUtils.h"

/*
 *								=== CGLParticleSystem ===
 */

/*
 *	CGLParticleSystem::CGLParticleSystem(int n_particle_num)
 *		- calculates texture sizes to contain n_particle_num particles
 *		  (chooses nearest greater or equal power of two), otherwise has no effect
 */
CGLParticleSystem::CGLParticleSystem(int n_particle_num)
	:m_p_core(0), m_b_particles_initialized(false), m_b_vertices_up_to_date(false)
{
	if(n_particle_num <= 0)
		n_particle_num = 1;
	// must have at least 1 particle, otherwise will fail to initialize textures

	int n_log2 = n_ceil_log2(n_particle_num);
	// get ceil(log2(n_particle_num))

	if(n_log2 > 31)
		n_log2 = 31; // can't go any higher
	m_n_page_width = 1 << ((n_log2 + 1) / 2);
	m_n_page_height = 1 << (n_log2 / 2);
	// calculate nearest pot page width and height
}

/*
 *	CGLParticleSystem::~CGLParticleSystem()
 *		- destroctor; deletes controller and all textures / vertex buffers it allocated
 */
CGLParticleSystem::~CGLParticleSystem()
{
	_Destroy();
}

/*
 *	static bool CGLParticleSystem::b_Supported()
 *		- returns true if extensions required for general mechanism of GPU
 *		  particle system are supported (in particular those are vertex and
 *		  pixel buffer objects and floating-point textures for vertices),
 *		  otherwise returns false
 */
bool CGLParticleSystem::b_Supported()
{
	return CGLVertexBufferObject::b_Supported() &&
		CGLPixelBufferObject::b_Supported() &&
		CGLExtensionHandler::b_SupportedExtension("GL_ARB_texture_float");
}

/*
 *	bool CGLParticleSystem::Set_Controller(CGLState *p_state, CCore *p_controller)
 *		- sets controller p_controller and makes it allocate textures and vertex objects
 *		- p_state is OpenGL state guard
 *		- returns true on success, false on failure
 *		- note p_controller is deleted by CGLParticleSystem destructor (or immediately,
 *		  in case it fails to initialize)
 *		- note in case of multiple calls to this function, previously specified controllers
 *		  are deleted along with all resources they might allocate
 */
bool CGLParticleSystem::Set_Controller(CGLState *p_state, CCore *p_controller)
{
	_Destroy();
	if(!p_controller->Alloc_Resources(p_state)) {
		delete p_controller;
		return false;
	}
	m_p_core = p_controller;
	m_b_particles_initialized = false;
	m_b_vertices_up_to_date = false;
	return true;
}

/*
 *	bool CGLParticleSystem::b_Status() const
 *		- returns true if particle system is ready, otherwise returns false
 */
bool CGLParticleSystem::b_Status() const
{
	return b_Supported() && m_p_core;
}

/*
 *	int CGLParticleSystem::n_Particle_Num() const
 *		- returns real number of particles
 *		- note this is power of two, greater or equal to number passed to constructor
 */
int CGLParticleSystem::n_Particle_Num() const
{
	return m_n_page_width * m_n_page_height;
}

/*
 *	CGLParticleSystem::CCore *CGLParticleSystem::p_Controller()
 *		- returns pointer to particle system controller or 0 in case
 *		  none has been successfully assigned
 */
CGLParticleSystem::CCore *CGLParticleSystem::p_Controller()
{
	return m_p_core;
}

/*
 *	const CGLParticleSystem::CCore *CGLParticleSystem::p_Controller() const
 *		- returns const pointer to particle system controller or 0 in case
 *		  none has been successfully assigned
 */
const CGLParticleSystem::CCore *CGLParticleSystem::p_Controller() const
{
	return m_p_core;
}

/*
 *	int CGLParticleSystem::n_VertexTexture_Width() const
 *		- returns width of vertex texture
 *		- note each pixel of vertex texture holds (part of) data of a single particle
 */
int CGLParticleSystem::n_VertexTexture_Width() const
{
	return m_n_page_width;
}

/*
 *	int CGLParticleSystem::n_VertexTexture_Height() const
 *		- returns height of vertex texture
 *		- note each pixel of vertex texture holds (part of) data of a single particle
 */
int CGLParticleSystem::n_VertexTexture_Height() const
{
	return m_n_page_height;
}

/*
 *	CGLTexture_2D *CGLParticleSystem::p_Alloc_Texture(CGLState *p_state,
 *		GLenum n_internal_format = GL_RGB16F_ARB, int n_width = -1, int n_height = -1)
 *		- allocates 2D texture with format n_internal_format and resolution
 *		  n_width per n_height
 *		- in case n_width or n_height is invalid (negative), corresponding
 *		  vertex texture dimension is used instead
 *		- texture wrap mode is set to GL_CLAMP and filtering is set to GL_NEAREST
 *		  (no mip-maps are generated)
 *		- returns new texture (valid) or 0 on error
 */
CGLTexture_2D *CGLParticleSystem::p_Alloc_Texture(CGLState *p_state,
	GLenum n_internal_format, int n_width, int n_height)
{
	if(!stl_ut::Reserve_1More(m_texture_list))
		return 0;
	// make sure there's space in the list

	if(n_width <= 0)
		n_width = m_n_page_width;
	if(n_height <= 0)
		n_height = m_n_page_height;
	// in case n_width or n_height is invalid (negative), corresponding
	// vertex texture dimension is used instead

	CGLTexture_2D *p_texture;
	if(!(p_texture = new(std::nothrow) CGLTexture_2D(p_state, n_width, n_height, n_internal_format, false)))
		return 0;
	if(!p_texture->b_Status()) {
		delete p_texture;
		return 0;
	}
	// create a new texture

	CGLTextureParams &r_params = p_texture->r_Parameters(p_state);
	r_params.Set_Texture_Wrap_S(GL_CLAMP);
	r_params.Set_Texture_Wrap_T(GL_CLAMP);
	r_params.Set_Texture_Min_Filter(GL_NEAREST);
	r_params.Set_Texture_Mag_Filter(GL_NEAREST);
	// set wrap to clamp and filtering to nearest neighbor

	m_texture_list.push_back(p_texture);
	// add texture to the list so it can be automatically deleted later

	return p_texture;
}

/*
 *	CGLVertexBufferObject *CGLParticleSystem::p_Alloc_VertexBuffer(CGLState *p_state,
 *		int n_particle_vertices_size, GLenum n_usage = GL_STATIC_DRAW_ARB)
 *		- allocates a new vertex buffer for all particles
 *		- n_particle_vertices_size is size of all vertices used for a single particles
 *		  (for example it might be 3 * sizeof(float) for point sprites (vertex per particle)
 *		  or 4 * 3 * sizeof(float) for screen-aligned quads (four vertices per particle))
 *		- returns new vertex buffer object (valid) or 0 on error
 */
CGLVertexBufferObject *CGLParticleSystem::p_Alloc_VertexBuffer(CGLState *p_state,
	int n_particle_vertices_size, GLenum n_usage)
{
	if(!stl_ut::Reserve_1More(m_vbo_list))
		return 0;
	// make sure there's space in the list

	CGLVertexBufferObject *p_vbo;
	if(!(p_vbo = new(std::nothrow) CGLVertexBufferObject(GL_ARRAY_BUFFER_ARB)))
		return 0;
	if(!p_vbo->Alloc(p_state, n_particle_vertices_size * n_Particle_Num(), 0, n_usage)) {
		delete p_vbo;
		return 0;
	}
	// alloc buffer

	p_state->BindVertexArrayBuffer(0);
	// bind no buffer

	m_vbo_list.push_back(p_vbo);
	// add vbo to the list so it can be automatically deleted later

	return p_vbo;
}

/*
 *	bool CGLParticleSystem::Reset(CGLState *p_state, float f_seed)
 *		- resets particle system (puts particles on their initial positions / etc)
 *		- f_seed is used to specify random seed (discussion: prefer int or float seed?)
 *		- p_state is OpenGL state guard
 *		- returns true on success, false on failure
 */
bool CGLParticleSystem::Reset(CGLState *p_state, float f_seed)
{
	if(m_p_core) {
		m_b_vertices_up_to_date = false;
		m_b_particles_initialized = false;
		if(!m_p_core->Reset_Textures(p_state, f_seed))
			return false;
		m_b_particles_initialized = true;
		return true;
	}
	return false;
}

/*
 *	bool CGLParticleSystem::Update(CGLState *p_state, float f_dt)
 *		- updates particle system; note in case it is not initialized,
 *		  calls Reset() with default seed 0
 *		- f_dt is delta-time step
 *		- p_state is OpenGL state guard
 *		- returns true on success, false on failure
 */
bool CGLParticleSystem::Update(CGLState *p_state, float f_dt)
{
	if(m_p_core) {
		m_b_vertices_up_to_date = false;
		if(!m_b_particles_initialized) {
			if(!m_p_core->Reset_Textures(p_state, 0)) // default seed
				return false;
			m_b_particles_initialized = true;
		}
		if(!m_p_core->Update_Textures(p_state, f_dt))
			return false;
		if(!m_p_core->Assemble_Vertices(p_state))
			return false;
		m_b_vertices_up_to_date = true;
	}
	return false;
}

/*
 *	bool CGLParticleSystem::Render(CGLState *p_state)
 *		- renders particle system; note in case it is not initialized,
 *		  calls Reset() with default seed 0
 *		- p_state is OpenGL state guard
 *		- returns true on success, false on failure
 */
bool CGLParticleSystem::Render(CGLState *p_state)
{
	if(m_p_core) {
		if(!m_b_particles_initialized && !Reset(p_state, 0)) // default seed
			return false;
		if(!m_b_vertices_up_to_date && !m_p_core->Assemble_Vertices(p_state))
			return false;
		m_b_vertices_up_to_date = true;
		return m_p_core->Render_Particles(p_state);
	}
	return false;
}

/*
 *	static void Copy_TextureToVBO(CGLState *p_state, CGLTexture_2D *p_texture,
 *		CGLVertexBufferObject *p_vbo, GLenum n_pixel_format = GL_RGB,
 *		GLenum n_data_type = GL_FLOAT, int n_offset = 0)
 *		- utility function for copying texture p_texture to VBO p_vbo
 *		- n_pixel_format is pixel format (not texture internal) for vertices
 *		  (use GL_RGB for 3D vertices), n_data_type is vertex component data type
 *		- p_state is OpenGL state guard
 *		- note copying is done inside server address space and therefore is very fast
 */
void CGLParticleSystem::Copy_TextureToVBO(CGLState *p_state, CGLTexture_2D *p_texture,
	CGLVertexBufferObject *p_vbo, GLenum n_pixel_format, GLenum n_data_type, int n_offset)
{
	p_texture->Bind_Enable(p_state);
	p_vbo->BindAs(p_state, GL_PIXEL_PACK_BUFFER_ARB);
	// bind texture and vertex buffer

	glGetTexImage(GL_TEXTURE_2D, 0, n_pixel_format,
		n_data_type, p_vbo->p_OffsetPointer(n_offset));
	// texture2d to vbo data upload (fast transfer inside card memory)

	p_state->BindPixelPackBuffer(0);
	// disable pixel-pack buffer
}

/*
 *	static int CGLParticleSystem::n_ceil_log2(unsigned int n_x)
 *		- calculates base 2 logarithm of n_x and rounds the result
 *		  to nearest greater / equal integer
 *		- returns ceil(log2(n_x))
 */
int CGLParticleSystem::n_ceil_log2(unsigned int n_x)
{
	if(!n_x)
		return 0;
	// log(0) = 0 ... as they say

	int n_high_bit_num = 0;
	int n_log2 = 0;
	for(int n_one_num = 0; n_x; n_x >>= 1, ++ n_log2)
		n_high_bit_num += n_x & 1;
	if(n_high_bit_num == 1)
		-- n_log2;
	// get ceil(log2(n_x))

	return n_log2;
}

/*
 *	void CGLParticleSystem::_Destroy()
 *		- cleanup function; deletes textures, vbo's and core object
 */
void CGLParticleSystem::_Destroy()
{
	std::for_each(m_texture_list.begin(), m_texture_list.end(), DeleteTexture);
	m_texture_list.clear();
	std::for_each(m_vbo_list.begin(), m_vbo_list.end(), DeleteVertexBuffer);
	m_vbo_list.clear();
	if(m_p_core) {
		delete m_p_core;
		m_p_core = 0;
	}
}

/*
 *	static inline void CGLParticleSystem::DeleteTexture(CGLTexture_2D *p_texture)
 *		- function object for deleting textures using std::for_each
 */
inline void CGLParticleSystem::DeleteTexture(CGLTexture_2D *p_texture)
{
	delete p_texture;
}

/*
 *	static inline void CGLParticleSystem::DeleteVertexBuffer(CGLVertexBufferObject *p_vbo)
 *		- function object for deleting textures using std::for_each
 */
inline void CGLParticleSystem::DeleteVertexBuffer(CGLVertexBufferObject *p_vbo)
{
	delete p_vbo;
}

/*
 *								=== ~CGLParticleSystem ===
 */
