// Copyright (C) 2002-2011 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#include "COctreeSceneNode.h"
#include "Octree.h"
#include "ISceneManager.h"
#include "IVideoDriver.h"
#include "ICameraSceneNode.h"
#include "IMeshCache.h"
#include "IAnimatedMesh.h"
#include "IMaterialRenderer.h"

#include "os.h"

namespace irr
{
namespace scene
{


//! constructor
COctreeSceneNode::COctreeSceneNode(ISceneNode* parent, ISceneManager* mgr,
					 s32 id, s32 minimalPolysPerNode)
	: IMeshSceneNode(parent, mgr, id), StdOctree(0), LightMapOctree(0),
	TangentsOctree(0), VertexType((video::E_VERTEX_TYPE)-1),
	MinimalPolysPerNode(minimalPolysPerNode), Mesh(0),
	UseVBOs(OCTREE_USE_HARDWARE), UseVisibilityAndVBOs(OCTREE_USE_VISIBILITY),
	BoxBased(OCTREE_BOX_BASED)
{
#ifdef _DEBUG
	setDebugName("COctreeSceneNode");
#endif
}


//! destructor
COctreeSceneNode::~COctreeSceneNode()
{
	deleteTree();
}


void COctreeSceneNode::OnRegisterSceneNode()
{
	if (IsVisible)
	{
		// because this node supports rendering of mixed mode meshes consisting of
		// transparent and solid material at the same time, we need to go through all
		// materials, check of what type they are and register this node for the right
		// render pass according to that.

		video::IVideoDriver* driver = SceneManager->getVideoDriver();

		PassCount = 0;
		u32 transparentCount = 0;
		u32 solidCount = 0;

		// count transparent and solid materials in this scene node
		for (u32 i=0; i<Materials.size(); ++i)
		{
			const video::IMaterialRenderer* const rnd =
				driver->getMaterialRenderer(Materials[i].MaterialType);

			if (rnd && rnd->isTransparent())
				++transparentCount;
			else
				++solidCount;

			if (solidCount && transparentCount)
				break;
		}

		// register according to material types counted

		if (solidCount)
			SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID);

		if (transparentCount)
			SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT);

		ISceneNode::OnRegisterSceneNode();
	}
}


//! renders the node.
void COctreeSceneNode::render()
{
	video::IVideoDriver* driver = SceneManager->getVideoDriver();

	if (VertexType == -1 || !driver)
		return;

	ICameraSceneNode* camera = SceneManager->getActiveCamera();
	if (!camera)
		return;

	bool isTransparentPass =
		SceneManager->getSceneNodeRenderPass() == scene::ESNRP_TRANSPARENT;
	++PassCount;

	driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);

	SViewFrustum frust = *camera->getViewFrustum();

	//transform the frustum to the current absolute transformation
	if ( !AbsoluteTransformation.isIdentity() )
	{
		core::matrix4 invTrans(AbsoluteTransformation, core::matrix4::EM4CONST_INVERSE);
		frust.transform(invTrans);
	}

	const core::aabbox3d<float> &box = frust.getBoundingBox();

	switch (VertexType)
	{
	case video::EVT_STANDARD:
		{
			if (BoxBased)
				StdOctree->calculatePolys(box);
			else
				StdOctree->calculatePolys(frust);

			const Octree<video::S3DVertex>::SIndexData* d = StdOctree->getIndexData();

			for (u32 i=0; i<Materials.size(); ++i)
			{
				if ( 0 == d[i].CurrentSize )
					continue;

				const video::IMaterialRenderer* const rnd = driver->getMaterialRenderer(Materials[i].MaterialType);
				const bool transparent = (rnd && rnd->isTransparent());

				// only render transparent buffer if this is the transparent render pass
				// and solid only in solid pass
				if (transparent == isTransparentPass)
				{
					driver->setMaterial(Materials[i]);
					driver->drawIndexedTriangleList(
						&StdMeshes[i].Vertices[0], StdMeshes[i].Vertices.size(),
						d[i].Indices, d[i].CurrentSize / 3);
				}
			}

			// for debug purposes only
			if (DebugDataVisible && !Materials.empty() && PassCount==1)
			{
				const core::aabbox3df& box = frust.getBoundingBox();
				core::array< const core::aabbox3d<f32>* > boxes;
				video::SMaterial m;
				m.Lighting = false;
				driver->setMaterial(m);
				if ( DebugDataVisible & scene::EDS_BBOX_BUFFERS )
				{
					StdOctree->getBoundingBoxes(box, boxes);
					for (u32 b=0; b!=boxes.size(); ++b)
						driver->draw3DBox(*boxes[b]);
				}

				if ( DebugDataVisible & scene::EDS_BBOX )
					driver->draw3DBox(Box,video::SColor(0,255,0,0));
			}
		}
		break;
	case video::EVT_2TCOORDS:
		{
			if (BoxBased)
				LightMapOctree->calculatePolys(box);
			else
				LightMapOctree->calculatePolys(frust);

			const Octree<video::S3DVertex2TCoords>::SIndexData* d = LightMapOctree->getIndexData();

			for (u32 i=0; i<Materials.size(); ++i)
			{
				if ( 0 == d[i].CurrentSize )
					continue;

				const video::IMaterialRenderer* const rnd = driver->getMaterialRenderer(Materials[i].MaterialType);
				const bool transparent = (rnd && rnd->isTransparent());

				// only render transparent buffer if this is the transparent render pass
				// and solid only in solid pass
				if (transparent == isTransparentPass)
				{
					driver->setMaterial(Materials[i]);
					if (UseVBOs)
					{
						if (UseVisibilityAndVBOs)
						{
							u16* oldPointer = LightMapMeshes[i].Indices.pointer();
							const u32 oldSize = LightMapMeshes[i].Indices.size();
							LightMapMeshes[i].Indices.set_free_when_destroyed(false);
							LightMapMeshes[i].Indices.set_pointer(d[i].Indices, d[i].CurrentSize, false, false);
							LightMapMeshes[i].setDirty(scene::EBT_INDEX);
							driver->drawMeshBuffer ( &LightMapMeshes[i] );
							LightMapMeshes[i].Indices.set_pointer(oldPointer, oldSize);
							LightMapMeshes[i].setDirty(scene::EBT_INDEX);
						}
						else
							driver->drawMeshBuffer ( &LightMapMeshes[i] );
					}
					else
						driver->drawIndexedTriangleList(
							&LightMapMeshes[i].Vertices[0],
							LightMapMeshes[i].Vertices.size(),
							d[i].Indices, d[i].CurrentSize / 3);
				}
			}

			// for debug purposes only
			if (DebugDataVisible && !Materials.empty() && PassCount==1)
			{
				const core::aabbox3d<float> &box = frust.getBoundingBox();
				core::array< const core::aabbox3d<f32>* > boxes;
				video::SMaterial m;
				m.Lighting = false;
				driver->setMaterial(m);
				if ( DebugDataVisible & scene::EDS_BBOX_BUFFERS )
				{
					LightMapOctree->getBoundingBoxes(box, boxes);
					for (u32 b=0; b<boxes.size(); ++b)
						driver->draw3DBox(*boxes[b]);
				}

				if ( DebugDataVisible & scene::EDS_BBOX )
					driver->draw3DBox(Box,video::SColor(0,255,0,0));
			}
		}
		break;
	case video::EVT_TANGENTS:
		{
			if (BoxBased)
				TangentsOctree->calculatePolys(box);
			else
				TangentsOctree->calculatePolys(frust);

			const Octree<video::S3DVertexTangents>::SIndexData* d =  TangentsOctree->getIndexData();

			for (u32 i=0; i<Materials.size(); ++i)
			{
				if ( 0 == d[i].CurrentSize )
					continue;

				const video::IMaterialRenderer* const rnd = driver->getMaterialRenderer(Materials[i].MaterialType);
				const bool transparent = (rnd && rnd->isTransparent());

				// only render transparent buffer if this is the transparent render pass
				// and solid only in solid pass
				if (transparent == isTransparentPass)
				{
					driver->setMaterial(Materials[i]);
					driver->drawIndexedTriangleList(
						&TangentsMeshes[i].Vertices[0], TangentsMeshes[i].Vertices.size(),
						d[i].Indices, d[i].CurrentSize / 3);
				}
			}

			// for debug purposes only
			if (DebugDataVisible && !Materials.empty() && PassCount==1)
			{
				const core::aabbox3d<float> &box = frust.getBoundingBox();
				core::array< const core::aabbox3d<f32>* > boxes;
				video::SMaterial m;
				m.Lighting = false;
				driver->setMaterial(m);
				if ( DebugDataVisible & scene::EDS_BBOX_BUFFERS )
				{
					TangentsOctree->getBoundingBoxes(box, boxes);
					for (u32 b=0; b<boxes.size(); ++b)
						driver->draw3DBox(*boxes[b]);
				}

				if ( DebugDataVisible & scene::EDS_BBOX )
					driver->draw3DBox(Box,video::SColor(0,255,0,0));
			}
		}
		break;
	}
}


//! returns the axis aligned bounding box of this node
const core::aabbox3d<f32>& COctreeSceneNode::getBoundingBox() const
{
	return Box;
}


//! creates the tree
bool COctreeSceneNode::createTree(IMesh* mesh)
{
	if (!mesh)
		return false;

	MeshName = SceneManager->getMeshCache()->getMeshName(mesh);

    mesh->grab();
	deleteTree();

	Mesh = mesh;

	u32 beginTime = os::Timer::getRealTime();

	u32 nodeCount = 0;
	u32 polyCount = 0;
	u32 i;

	Box = mesh->getBoundingBox();

	if (mesh->getMeshBufferCount())
	{
		VertexType = mesh->getMeshBuffer(0)->getVertexType();

		switch(VertexType)
		{
		case video::EVT_STANDARD:
			{
				for (i=0; i<mesh->getMeshBufferCount(); ++i)
				{
					IMeshBuffer* b = mesh->getMeshBuffer(i);

					if (b->getVertexCount() && b->getIndexCount())
					{
						Materials.push_back(b->getMaterial());

						StdMeshes.push_back(Octree<video::S3DVertex>::SMeshChunk());
						Octree<video::S3DVertex>::SMeshChunk &nchunk = StdMeshes.getLast();
						nchunk.MaterialId = Materials.size() - 1;

						u32 v;
						nchunk.Vertices.reallocate(b->getVertexCount());
						for (v=0; v<b->getVertexCount(); ++v)
							nchunk.Vertices.push_back(((video::S3DVertex*)b->getVertices())[v]);

						polyCount += b->getIndexCount();

						nchunk.Indices.reallocate(b->getIndexCount());
						for (v=0; v<b->getIndexCount(); ++v)
							nchunk.Indices.push_back(b->getIndices()[v]);
					}
				}

				StdOctree = new Octree<video::S3DVertex>(StdMeshes, MinimalPolysPerNode);
				nodeCount = StdOctree->getNodeCount();
			}
			break;
		case video::EVT_2TCOORDS:
			{
				IMeshBuffer* b;
				u32 meshReserve = 0;
				for ( i=0; i < mesh->getMeshBufferCount(); ++i)
				{
					b = mesh->getMeshBuffer(i);
					if (b->getVertexCount() && b->getIndexCount())
					{
						meshReserve += 1;
					}

				}
				LightMapMeshes.reallocate ( LightMapMeshes.size() + meshReserve );

				for ( i=0; i < mesh->getMeshBufferCount(); ++i)
				{
					b = mesh->getMeshBuffer(i);

					if (b->getVertexCount() && b->getIndexCount())
					{
						Materials.push_back(b->getMaterial());
						LightMapMeshes.push_back(Octree<video::S3DVertex2TCoords>::SMeshChunk());
						Octree<video::S3DVertex2TCoords>::SMeshChunk& nchunk = LightMapMeshes.getLast();
						nchunk.MaterialId = Materials.size() - 1;

						if (UseVisibilityAndVBOs)
						{
							nchunk.setHardwareMappingHint(scene::EHM_STATIC, scene::EBT_VERTEX);
							nchunk.setHardwareMappingHint(scene::EHM_DYNAMIC, scene::EBT_INDEX);
						}
						else
							nchunk.setHardwareMappingHint(scene::EHM_STATIC);

						u32 v;
						nchunk.Vertices.reallocate(b->getVertexCount());
						for (v=0; v<b->getVertexCount(); ++v)
							nchunk.Vertices.push_back(((video::S3DVertex2TCoords*)b->getVertices())[v]);

						polyCount += b->getIndexCount();
						nchunk.Indices.reallocate(b->getIndexCount());
						for (v=0; v<b->getIndexCount(); ++v)
							nchunk.Indices.push_back(b->getIndices()[v]);
					}
				}

				LightMapOctree = new Octree<video::S3DVertex2TCoords>(LightMapMeshes, MinimalPolysPerNode);
				nodeCount = LightMapOctree->getNodeCount();
			}
			break;
		case video::EVT_TANGENTS:
			{
				for (u32 i=0; i<mesh->getMeshBufferCount(); ++i)
				{
					IMeshBuffer* b = mesh->getMeshBuffer(i);

					if (b->getVertexCount() && b->getIndexCount())
					{
						Materials.push_back(b->getMaterial());
						TangentsMeshes.push_back(Octree<video::S3DVertexTangents>::SMeshChunk());
						Octree<video::S3DVertexTangents>::SMeshChunk& nchunk = TangentsMeshes.getLast();
						nchunk.MaterialId = Materials.size() - 1;

						u32 v;
						nchunk.Vertices.reallocate(b->getVertexCount());
						for (v=0; v<b->getVertexCount(); ++v)
							nchunk.Vertices.push_back(((video::S3DVertexTangents*)b->getVertices())[v]);

						polyCount += b->getIndexCount();
						nchunk.Indices.reallocate(b->getIndexCount());
						for (v=0; v<b->getIndexCount(); ++v)
							nchunk.Indices.push_back(b->getIndices()[v]);
					}
				}

				TangentsOctree = new Octree<video::S3DVertexTangents>(TangentsMeshes, MinimalPolysPerNode);
				nodeCount = TangentsOctree->getNodeCount();
			}
			break;
		}
	}

	u32 endTime = os::Timer::getRealTime();
	c8 tmp[255];
	sprintf(tmp, "Needed %ums to create Octree SceneNode.(%u nodes, %u polys)",
		endTime - beginTime, nodeCount, polyCount/3);
	os::Printer::log(tmp, ELL_INFORMATION);

	return true;
}


//! returns the material based on the zero based index i. To get the amount
//! of materials used by this scene node, use getMaterialCount().
//! This function is needed for inserting the node into the scene hirachy on a
//! optimal position for minimizing renderstate changes, but can also be used
//! to directly modify the material of a scene node.
video::SMaterial& COctreeSceneNode::getMaterial(u32 i)
{
	if ( i >= Materials.size() )
		return ISceneNode::getMaterial(i);

	return Materials[i];
}


//! returns amount of materials used by this scene node.
u32 COctreeSceneNode::getMaterialCount() const
{
	return Materials.size();
}


//! Writes attributes of the scene node.
void COctreeSceneNode::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const
{
	ISceneNode::serializeAttributes(out, options);

	out->addInt("MinimalPolysPerNode", MinimalPolysPerNode);
	out->addString("Mesh", MeshName.c_str());
}


//! Reads attributes of the scene node.
void COctreeSceneNode::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options)
{
	const s32 oldMinimal = MinimalPolysPerNode;

	MinimalPolysPerNode = in->getAttributeAsInt("MinimalPolysPerNode");
	io::path newMeshStr = in->getAttributeAsString("Mesh");

	IMesh* newMesh = 0;

	if (newMeshStr == "")
		newMeshStr = MeshName;

	IAnimatedMesh* newAnimatedMesh = SceneManager->getMesh(newMeshStr.c_str());

	if (newAnimatedMesh)
		newMesh = newAnimatedMesh->getMesh(0);

	if (newMesh && ((MeshName != newMeshStr) || (MinimalPolysPerNode != oldMinimal)))
	{
		// recalculate tree
		createTree(newMesh);
	}

	ISceneNode::deserializeAttributes(in, options);
}


void COctreeSceneNode::deleteTree()
{
	delete StdOctree;
	StdOctree = 0;
	StdMeshes.clear();

	delete LightMapOctree;
	LightMapOctree = 0;
	LightMapMeshes.clear();

	delete TangentsOctree;
	TangentsOctree = 0;
	TangentsMeshes.clear();

	Materials.clear();

	if(Mesh)
		Mesh->drop();
}

void COctreeSceneNode::setMesh(IMesh* mesh)
{
	createTree(mesh);
}

IMesh* COctreeSceneNode::getMesh(void)
{
	return Mesh;
}

void COctreeSceneNode::setReadOnlyMaterials(bool readonly)
{
	// Do nothing
}

bool COctreeSceneNode::isReadOnlyMaterials() const
{
	return false;
}


} // end namespace scene
} // end namespace irr

