/**
 * =========================================================================
 * File        : ModelRenderer.cpp
 * Project     : Pyrogenesis
 * Description : Implementation of ModelRenderer and BatchModelRenderer
 *
 * @author Nicolai Hähnle <nicolai@wildfiregames.com>
 * =========================================================================
 */

#include "precompiled.h"

#include "lib/ogl.h"
#include "maths/Vector3D.h"
#include "maths/Vector4D.h"

#include "ps/CLogger.h"
#include "ps/Profile.h"

#include "graphics/Color.h"
#include "graphics/LightEnv.h"
#include "graphics/Model.h"
#include "graphics/ModelDef.h"

#include "renderer/ModelRenderer.h"
#include "renderer/ModelVertexRenderer.h"
#include "renderer/Renderer.h"
#include "renderer/RenderModifiers.h"


#define LOG_CATEGORY "graphics"


/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SkinPoint: skin the vertex position using it's blend data and given bone matrices
static void SkinPoint(const SModelVertex& vertex,const CMatrix3D* matrices,CVector3D& result)
{
	CVector3D tmp;
	const SVertexBlend& blend=vertex.m_Blend;

	// must have at least one valid bone if we're using SkinPoint
	debug_assert(blend.m_Bone[0]!=0xff);

	const CMatrix3D& m=matrices[blend.m_Bone[0]];
	m.Transform(vertex.m_Coords,result);
	result*=blend.m_Weight[0];

	for (u32 i=1; i<SVertexBlend::SIZE && blend.m_Bone[i]!=0xff; i++) {
		const CMatrix3D& m=matrices[blend.m_Bone[i]];
		m.Transform(vertex.m_Coords,tmp);
		result+=tmp*blend.m_Weight[i];
	}
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SkinPoint: skin the vertex normal using it's blend data and given bone matrices
static void SkinNormal(const SModelVertex& vertex, const CMatrix3D* invtranspmatrices, CVector3D& result)
{
	CVector3D tmp;
	const SVertexBlend& blend=vertex.m_Blend;

	// must have at least one valid bone if we're using SkinNormal
	debug_assert(blend.m_Bone[0]!=0xff);

	const CMatrix3D& m = invtranspmatrices[blend.m_Bone[0]];
	m.Rotate(vertex.m_Norm, result);
	result*=blend.m_Weight[0];

	for (u32 i=1; i<SVertexBlend::SIZE && vertex.m_Blend.m_Bone[i]!=0xff; i++) {
		const CMatrix3D& m = invtranspmatrices[blend.m_Bone[i]];
		m.Rotate(vertex.m_Norm,tmp);
		result+=tmp*blend.m_Weight[i];
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////
// ModelRenderer implementation

// Helper function to copy object-space position and normal vectors into arrays.
void ModelRenderer::CopyPositionAndNormals(
		CModelDefPtr mdef,
		VertexArrayIterator<CVector3D> Position,
		VertexArrayIterator<CVector3D> Normal)
{
	size_t numVertices = mdef->GetNumVertices();
	SModelVertex* vertices = mdef->GetVertices();

	for(size_t j = 0; j < numVertices; ++j)
	{
		Position[j] = vertices[j].m_Coords;
		Normal[j] = vertices[j].m_Norm;
	}
}

// Helper function to transform position and normal vectors into world-space.
void ModelRenderer::BuildPositionAndNormals(
		CModel* model,
		VertexArrayIterator<CVector3D> Position,
		VertexArrayIterator<CVector3D> Normal)
{
	CModelDefPtr mdef = model->GetModelDef();
	size_t numVertices = mdef->GetNumVertices();
	SModelVertex* vertices=mdef->GetVertices();

	const CMatrix3D* bonematrices = model->GetBoneMatrices();
	if (bonematrices)
	{
		// boned model - calculate skinned vertex positions/normals
		PROFILE( "skinning bones" );
		const CMatrix3D* invtranspbonematrices;

		// Analytic geometry tells us that normal vectors need to be
		// multiplied by the inverse of the transpose. However, calculating
		// the inverse is slow, and analytic geometry also tells us that
		// for orthogonal matrices, the inverse is equal to the transpose,
		// so the inverse of the transpose is, in fact, the original matrix.
		//
		// The "fast normals" code assumes that bone transformation contain
		// no "weird" transformations like shears or non-uniform scaling
		// (actually, the entire code assumes no scaling) and thus gets
		// around the slow calculation of the inverse.
		if (g_Renderer.m_FastNormals)
			invtranspbonematrices = bonematrices;
		else
			invtranspbonematrices = model->GetInvTranspBoneMatrices();

		// Avoid the noisy warnings that occur inside SkinPoint/SkinNormal in
		// some broken situations
		if (numVertices && vertices[0].m_Blend.m_Bone[0] == 0xff)
		{
			LOG_ONCE(ERROR, LOG_CATEGORY, "Model %s is boned with unboned animation", mdef->GetName().c_str());
			return;
		}

		for (size_t j=0; j<numVertices; j++)
		{
			SkinPoint(vertices[j], bonematrices, Position[j]);
			SkinNormal(vertices[j], invtranspbonematrices, Normal[j]);
		}
	}
	else
	{
		PROFILE( "software transform" );
		// just copy regular positions, transform normals to world space
		const CMatrix3D& transform = model->GetTransform();
		const CMatrix3D& invtransform = model->GetInvTransform();
		for (uint j=0; j<numVertices; j++)
		{
			transform.Transform(vertices[j].m_Coords,Position[j]);
			invtransform.RotateTransposed(vertices[j].m_Norm,Normal[j]);
		}
	}
}


// Helper function for lighting
void ModelRenderer::BuildColor4ub(
		CModel* model,
		VertexArrayIterator<CVector3D> Normal,
		VertexArrayIterator<SColor4ub> Color,
		bool onlyDiffuse)
{
	PROFILE( "lighting vertices" );

	CModelDefPtr mdef = model->GetModelDef();
	size_t numVertices = mdef->GetNumVertices();
	const CLightEnv& lightEnv = g_Renderer.GetLightEnv();
	CColor shadingColor = model->GetShadingColor();
	RGBColor tempcolor;

	if (onlyDiffuse)
	{
		for (uint j=0; j<numVertices; j++)
		{
			lightEnv.EvaluateDirect(Normal[j], tempcolor);
			tempcolor.X *= shadingColor.r;
			tempcolor.Y *= shadingColor.g;
			tempcolor.Z *= shadingColor.b;
			*(u32*)&Color[j] = ConvertRGBColorTo4ub(tempcolor);
		}
	}
	else
	{
		for (uint j=0; j<numVertices; j++)
		{
			lightEnv.EvaluateUnit(Normal[j], tempcolor);
			tempcolor.X *= shadingColor.r;
			tempcolor.Y *= shadingColor.g;
			tempcolor.Z *= shadingColor.b;
			*(u32*)&Color[j] = ConvertRGBColorTo4ub(tempcolor);
		}
	}
}


// Copy UV coordinates
void ModelRenderer::BuildUV(
		CModelDefPtr mdef,
		VertexArrayIterator<float[2]> UV)
{
	size_t numVertices = mdef->GetNumVertices();
	SModelVertex* vertices = mdef->GetVertices();

	for (uint j=0; j < numVertices; ++j, ++UV)
	{
		(*UV)[0] = vertices[j].m_U;
		(*UV)[1] = 1.0-vertices[j].m_V;
	}
}


// Build default indices array.
void ModelRenderer::BuildIndices(
		CModelDefPtr mdef,
		u16* Indices)
{
	u32 idxidx = 0;
	SModelFace* faces = mdef->GetFaces();

	for (size_t j = 0; j < mdef->GetNumFaces(); ++j) {
		SModelFace& face=faces[j];
		Indices[idxidx++]=face.m_Verts[0];
		Indices[idxidx++]=face.m_Verts[1];
		Indices[idxidx++]=face.m_Verts[2];
	}
}



///////////////////////////////////////////////////////////////////////////////////////////////
// BatchModelRenderer implementation


/// See BatchModelRendererInternals::phase
enum BMRPhase {
	/// Currently allow calls to Submit and PrepareModels
	BMRSubmit,

	/// Allow calls to rendering and EndFrame
	BMRRender
};


/**
 * Struct BMRModelData: Per-CModel render data used by the BatchModelRenderer.
 */
struct BMRModelData : public CModelRData
{
	BMRModelData(BatchModelRendererInternals* bmri, CModel* model)
	: CModelRData(bmri, model), m_BMRI(bmri), m_Data(0), m_Next(0) { }
	virtual ~BMRModelData();

	/// Back-link to "our" modelrenderer
	BatchModelRendererInternals* m_BMRI;

	/// Private data created by derived class' CreateModelData
	void* m_Data;

	/// Next model in the per-ModelDefTracker-slot linked list.
	BMRModelData* m_Next;
};


/**
 * Class BMRModelDefTracker: Per-CModelDef data used by the BatchModelRenderer.
 *
 * Note that classes that derive from BatchModelRenderer should use
 * their own per-CModelDef data if necessary.
 */
struct BMRModelDefTracker : public CModelDefRPrivate
{
	BMRModelDefTracker(CModelDefPtr mdef)
	: m_ModelDef(mdef), m_Next(0), m_Slots(0) { }

	/// Back-link to the CModelDef object
	boost::weak_ptr<CModelDef> m_ModelDef;

	/// Pointer to the next ModelDefTracker that has submitted models.
	BMRModelDefTracker* m_Next;

	/// Number of slots used in m_ModelSlots
	uint m_Slots;

	/// Each slot contains a linked list of model data objects, up to m_Slots-1
	// At the end of the frame, m_Slots is reset to 0, but m_ModelSlots stays
	// the same size (we assume the same number of slots is going to be used
	// next frame)
	std::vector<BMRModelData*> m_ModelSlots;
};



/**
 * Struct BatchModelRendererInternals: Internal data of the BatchModelRenderer
 *
 * Separated into the source file to increase implementation hiding (and to
 * avoid some causes of recompiles).
 */
struct BatchModelRendererInternals
{
	BatchModelRendererInternals(BatchModelRenderer* r) : m_Renderer(r) { }

	/// Back-link to "our" renderer
	BatchModelRenderer* m_Renderer;

	/// ModelVertexRenderer used for vertex transformations
	ModelVertexRendererPtr vertexRenderer;

	/// Track the current "phase" of the frame (only for debugging purposes)
	BMRPhase phase;

	/// Linked list of ModelDefTrackers that have submitted models
	BMRModelDefTracker* submissions;

	/// Helper functions
	void ThunkDestroyModelData(CModel* model, void* data)
	{
		vertexRenderer->DestroyModelData(model, data);
	}

	void RenderAllModels(RenderModifierPtr modifier, u32 filterflags, uint pass, uint streamflags);
};

BMRModelData::~BMRModelData()
{
	m_BMRI->ThunkDestroyModelData(GetModel(), m_Data);
}


// Construction/Destruction
BatchModelRenderer::BatchModelRenderer(ModelVertexRendererPtr vertexrenderer)
{
	m = new BatchModelRendererInternals(this);
	m->vertexRenderer = vertexrenderer;
	m->phase = BMRSubmit;
	m->submissions = 0;
}

BatchModelRenderer::~BatchModelRenderer()
{
	delete m;
}

// Submit one model.
void BatchModelRenderer::Submit(CModel* model)
{
	debug_assert(m->phase == BMRSubmit);

	CModelDefPtr mdef = model->GetModelDef();
	BMRModelDefTracker* mdeftracker = (BMRModelDefTracker*)mdef->GetRenderData(m);
	CModelRData* rdata = (CModelRData*)model->GetRenderData();
	BMRModelData* bmrdata = 0;

	// Ensure model def data and model data exist
	if (!mdeftracker)
	{
		mdeftracker = new BMRModelDefTracker(mdef);
		mdef->SetRenderData(m, mdeftracker);
	}

	if (rdata && rdata->GetKey() == m)
	{
		bmrdata = (BMRModelData*)rdata;
	}
	else
	{
		bmrdata = new BMRModelData(m, model);
		bmrdata->m_Data = m->vertexRenderer->CreateModelData(model);
		rdata = bmrdata;
		model->SetRenderData(bmrdata);
		model->SetDirty(~0u);
		g_Renderer.LoadTexture(model->GetTexture(), GL_CLAMP_TO_EDGE);
	}

	// Add the model def tracker to the submission list if necessary
	if (!mdeftracker->m_Slots)
	{
		mdeftracker->m_Next = m->submissions;
		m->submissions = mdeftracker;
	}

	// Add the bmrdata to the modeldef list
	Handle htex = model->GetTexture()->GetHandle();
	uint idx;

	for(idx = 0; idx < mdeftracker->m_Slots; ++idx)
	{
		BMRModelData* in = mdeftracker->m_ModelSlots[idx];

		if (in->GetModel()->GetTexture()->GetHandle() == htex)
			break;
	}

	if (idx >= mdeftracker->m_Slots)
	{
		++mdeftracker->m_Slots;
		if (mdeftracker->m_Slots > mdeftracker->m_ModelSlots.size())
		{
			mdeftracker->m_ModelSlots.push_back(0);
			debug_assert(mdeftracker->m_ModelSlots.size() == mdeftracker->m_Slots);
		}
		mdeftracker->m_ModelSlots[idx] = 0;
	}

	bmrdata->m_Next = mdeftracker->m_ModelSlots[idx];
	mdeftracker->m_ModelSlots[idx] = bmrdata;
}


// Call update for all submitted models and enter the rendering phase
void BatchModelRenderer::PrepareModels()
{
	debug_assert(m->phase == BMRSubmit);

	for(BMRModelDefTracker* mdeftracker = m->submissions; mdeftracker; mdeftracker = mdeftracker->m_Next)
	{
		for(uint idx = 0; idx < mdeftracker->m_Slots; ++idx)
		{
			for(BMRModelData* bmrdata = mdeftracker->m_ModelSlots[idx]; bmrdata; bmrdata = bmrdata->m_Next)
			{
				CModel* model = bmrdata->GetModel();

				debug_assert(model->GetRenderData() == bmrdata);

				m->vertexRenderer->UpdateModelData(
						model, bmrdata->m_Data,
						bmrdata->m_UpdateFlags);
				bmrdata->m_UpdateFlags = 0;
			}
		}
	}

	m->phase = BMRRender;
}


// Clear the submissions list
void BatchModelRenderer::EndFrame()
{
	static uint mostslots = 1;

	for(BMRModelDefTracker* mdeftracker = m->submissions; mdeftracker; mdeftracker = mdeftracker->m_Next)
	{
		if (mdeftracker->m_Slots > mostslots)
		{
			mostslots = mdeftracker->m_Slots;
			debug_printf("BatchModelRenderer: SubmissionSlots maximum: %u\n", mostslots);
		}
		mdeftracker->m_Slots = 0;
	}
	m->submissions = 0;

	m->phase = BMRSubmit;
}


// Return whether we models have been submitted this frame
bool BatchModelRenderer::HaveSubmissions()
{
	return m->submissions != 0;
}


// Render models, outer loop for multi-passing
void BatchModelRenderer::Render(RenderModifierPtr modifier, u32 flags)
{
	debug_assert(m->phase == BMRRender);

	if (!HaveSubmissions())
		return;

	uint pass = 0;

	do
	{
		uint streamflags = modifier->BeginPass(pass);
		const CMatrix3D* texturematrix = 0;

		if (streamflags & STREAM_TEXGENTOUV1)
			texturematrix = modifier->GetTexGenMatrix(pass);

		m->vertexRenderer->BeginPass(streamflags, texturematrix);

		m->RenderAllModels(modifier, flags, pass, streamflags);

		m->vertexRenderer->EndPass(streamflags);
	} while(!modifier->EndPass(pass++));
}


// Render one frame worth of models
void BatchModelRendererInternals::RenderAllModels(
		RenderModifierPtr modifier, u32 filterflags,
		uint pass, uint streamflags)
{
	for(BMRModelDefTracker* mdeftracker = submissions; mdeftracker; mdeftracker = mdeftracker->m_Next)
	{
		vertexRenderer->PrepareModelDef(streamflags, mdeftracker->m_ModelDef.lock());

		for(uint idx = 0; idx < mdeftracker->m_Slots; ++idx)
		{
			BMRModelData* bmrdata = mdeftracker->m_ModelSlots[idx];

			modifier->PrepareTexture(pass, bmrdata->GetModel()->GetTexture());

			for(; bmrdata; bmrdata = bmrdata->m_Next)
			{
				CModel* model = bmrdata->GetModel();

				debug_assert(bmrdata->GetKey() == this);

				if (filterflags && !(model->GetFlags()&filterflags))
					continue;

				modifier->PrepareModel(pass, model);
				vertexRenderer->RenderModel(streamflags, model, bmrdata->m_Data);
			}
		}
	}
}

