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

#include "IrrCompileConfig.h"
#include "CLODMeshSceneNode.h"
#include "IVideoDriver.h"
#include "ISceneManager.h"
#include "S3DVertex.h"
#include "SViewFrustrum.h"
#include "ICameraSceneNode.h"

namespace irr
{
namespace scene
{

	//! constructor
	CLODMeshSceneNode::CLODMeshSceneNode( IMesh* smallMesh, IMesh* mediumMesh, IMesh* largeMesh, 
		f64 smallMeshDistance, f64 mediumMeshDistance, ISceneNode* parent, ISceneManager* mgr, s32 id,
		const core::vector3df& position, const core::vector3df& rotation, const core::vector3df& scale )
	: ISceneNode(parent, mgr, id, position, rotation, scale), SmallMesh(smallMesh), MediumMesh(mediumMesh)
	, LargeMesh(largeMesh), ActiveMesh(LargeMesh), PassCount(0)
	{
		#ifdef _DEBUG
		setDebugName("CLODMeshSceneNode");
		#endif

		if( SmallMesh && MediumMesh && LargeMesh )
		{
			// get materials.
			video::SMaterial mat;
			for(u32 i=0; i<SmallMesh->getMeshBufferCount(); ++i)
			{
				IMeshBuffer* mb = SmallMesh->getMeshBuffer(i);
				if( mb )
					mat = mb->getMaterial();

				Materials.push_back(mat);
			}

			// grab the mesh
			SmallMesh->grab();
			MediumMesh->grab();
			LargeMesh->grab();
		}
	}



	//! destructor
	CLODMeshSceneNode::~CLODMeshSceneNode()
	{
		if(SmallMesh)
			SmallMesh->drop();
		if(MediumMesh)
			MediumMesh->drop();
		if(LargeMesh)
			LargeMesh->drop();
	}


	//! frame
	void CLODMeshSceneNode::OnPreRender( const u32& timeMs )
	{
		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;

			if( RenderPass = ESNRP_AUTOMATIC )
			{
				s32 transparentCount = 0;
				s32 solidCount = 0;

				// count transparent and solid materials in this scene node
				for( u32 i=0; i<Materials.size(); ++i )
				{
					video::IMaterialRenderer* 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 );
			}
			else
			{
				SceneManager->registerNodeForRendering( this, RenderPass );
			}

			// Determine which mesh to draw based on distance from Active Camera
			f64 distance = getAbsolutePosition().getDistanceFrom( SceneManager->getActiveCamera()->getAbsolutePosition() );
			if( distance <= SmallMeshDistance )
			{
				if( distance <= MediumMeshDistance )
				{
					ActiveMesh = LargeMesh;
				}
				else
				{
					ActiveMesh = MediumMesh;
				}
			}
			else
			{
				ActiveMesh = SmallMesh;
			}

			core::list< ISceneNode* >::Iterator it = Children.begin();
			for( ; it != Children.end(); ++it )
				(*it)->OnPreRender( timeMs );
		}
	}


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

		if( !ActiveMesh || !driver )
			return;

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

		driver->setTransform( video::ETS_WORLD, AbsoluteTransformation );
		Box = ActiveMesh->getBoundingBox();

		// for debug purposes only:
		if( DebugDataVisible && PassCount == 1 )
		{
			video::SMaterial m;
			m.Lighting = false;
			driver->setMaterial(m);
			driver->draw3DBox(Box, video::SColor(0,255,255,255));

#if 0
			// draw normals
			for( u32 g=0; g<Mesh->getMeshBufferCount(); ++g )
			{
				scene::IMeshBuffer* mb = Mesh->getMeshBuffer( g );

				u32 vSize;
				u32 i;
				vSize = 0;
				switch( mb->getVertexType() )
				{
					case video::EVT_STANDARD:
						vSize = sizeof ( video::S3DVertex );
						break;
					case video::EVT_2TCOORDS:
						vSize = sizeof ( video::S3DVertex2TCoords );
						break;
					case video::EVT_TANGENTS:
						vSize = sizeof ( video::S3DVertexTangents );
						break;
				}

				const video::S3DVertex* v = ( const video::S3DVertex*)mb->getVertices();
				video::SColor c ( 255, 128 ,0, 0 );
				video::SColor c1 ( 255, 255 ,0, 0 );
				for( i = 0; i != mb->getVertexCount(); ++i )
				{
					core::vector3df h = v->Normal * 5.f;
					core::vector3df h1 = h.crossProduct ( core::vector3df ( 0.f, 1.f, 0.f ) );

					driver->draw3DLine ( v->Pos, v->Pos + h, c );
					driver->draw3DLine ( v->Pos + h, v->Pos + h + h1, c1 );
					v = (const video::S3DVertex*) ( (u8*) v + vSize );
				}

			}
#endif
		}

		for( u32 i=0; i<ActiveMesh->getMeshBufferCount(); ++i )
		{
			video::IMaterialRenderer* rnd = driver->getMaterialRenderer( Materials[i].MaterialType );
			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 )
			{
				scene::IMeshBuffer* mb = ActiveMesh->getMeshBuffer( i );
				driver->setMaterial( Materials[i] );
				driver->drawMeshBuffer( mb );
			}
		}			
	}


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


	//! 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& CLODMeshSceneNode::getMaterial(const u32& i)
	{
		if(i < 0 || i >= (s32)Materials.size())
			return ISceneNode::getMaterial(i);

		return Materials[i];
	}



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



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

