// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "std_afx.h"

#undef OBJECT_VIEWER_EXPORT
#define OBJECT_VIEWER_EXPORT __declspec( dllexport )

#include <vector>


#include "object_viewer.h"

#include "nel/3d/nelu.h"
#include "nel/3d/mesh.h"
#include "nel/3d/mesh_mrm.h"
#include "nel/3d/mesh_mrm_skinned.h"
#include "nel/3d/transform_shape.h"
#include "nel/3d/mesh_instance.h"
#include "nel/3d/text_context.h"
#include "nel/3d/skeleton_model.h"
#include "nel/3d/init_3d.h"
#include "nel/3d/scene_group.h"
#include "nel/3d/animation_playlist.h"
#include "nel/3d/track_keyframer.h"
#include "nel/3d/font_generator.h"
#include "nel/3d/register_3d.h"
#include "nel/3d/seg_remanence.h"

#include "nel/misc/common.h"
#include "nel/misc/file.h"
#include "nel/misc/path.h"
#include "nel/misc/time_nl.h"
#include "nel/misc/config_file.h"

#include "nel/sound/u_audio_mixer.h"
#include "nel/3d/water_pool_manager.h"
#include "nel/3d/landscape_model.h"
#include "nel/3d/visual_collision_manager.h"
#include "nel/3d/visual_collision_entity.h"
#include "nel/3d/ps_util.h"


#include "nel/pacs/global_retriever.h"



#include "select_movie_size.h"
#include "editable_range.h"
#include "range_manager.h"
#include "located_properties.h"
#include "color_button.h"
#include "particle_dlg.h"
#include "resource.h"
#include "main_frame.h"
#include "sound_system.h"
#include "scheme_manager.h"
#include "day_night_dlg.h"
#include "water_pool_editor.h"
#include "vegetable_dlg.h"
#include "dialog_progress.h"
#include "select_string.h"
#include "global_wind_dlg.h"
#include "sound_anim_dlg.h"
#include "light_group_factor.h"
#include "choose_bg_color_dlg.h"
#include "choose_sun_color_dlg.h"
#include "choose_frame_delay.h"
#include "skeleton_scale_dlg.h"
#include "graph.h"
#include "tune_mrm_dlg.h"


using namespace std;
using namespace NL3D;
using namespace NLMISC;
using namespace NLSOUND;
using namespace NLPACS;



				

static char SDrive[256];
static char SDir[256];

uint SkeletonUsedForSound = 0xFFFFFFFF;
CSoundContext SoundContext;


//
//	Note!
//
//		If this DLL is dynamically linked against the MFC
//		DLLs, any functions exported from this DLL which
//		call into MFC must have the AFX_MANAGE_STATE macro
//		added at the very beginning of the function.
//
//		For example:
//
//		extern "C" BOOL PASCAL EXPORT ExportedFunction()
//		{
//			AFX_MANAGE_STATE(AfxGetStaticModuleState());
//			// normal function body here
//		}
//
//		It is very important that this macro appear in each
//		function, prior to any calls into MFC.  This means that
//		it must appear as the first statement within the 
//		function, even before any object variable declarations
//		as their constructors may generate calls into the MFC
//		DLL.
//
//		Please see MFC Technical Notes 33 and 58 for additional
//		details.
//

/////////////////////////////////////////////////////////////////////////////
// CObject_viewerApp

BEGIN_MESSAGE_MAP(CObject_viewerApp, CWinApp)
	//{{AFX_MSG_MAP(CObject_viewerApp)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CObject_viewerApp construction

CObject_viewerApp::CObject_viewerApp()
{
	// TODO: add construction code here,
	// Place all significant initialization in InitInstance
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CObject_viewerApp object

CObject_viewerApp theApp;


bool CObjectViewer::_InstanceRunning = false;

// ***************************************************************************

class CObjView : public CView
{
public:
	CObjView() 
	{
		MainFrame=NULL;	
	};
	virtual ~CObjView() {}
	virtual void OnDraw (CDC *) {}
	afx_msg BOOL OnEraseBkgnd(CDC* pDC) 
	{ 
		return FALSE; 
	}
	virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
	{
		if ((CNELU::Driver) && MainFrame)
			MainFrame->DriverWindowProc (CNELU::Driver, m_hWnd, message, wParam, lParam);
			
		return CView::WindowProc(message, wParam, lParam);
	}
	DECLARE_DYNCREATE(CObjView)
	CMainFrame	*MainFrame;
};

// ***************************************************************************

IMPLEMENT_DYNCREATE(CObjView, CView)


// ***************************************************************************

void animateCNELUScene (CCloudScape *cs, uint64 deltaTime = 0)
{
	if (!cs) return;
	static sint64 firstTime = NLMISC::CTime::getLocalTime();
	static sint64 lastTime = NLMISC::CTime::getLocalTime();
	if (deltaTime == 0)
	{
		deltaTime = NLMISC::CTime::getLocalTime() - lastTime;
	}
	lastTime += deltaTime;
	float fdelta = 0.001f * (float) (lastTime - firstTime);
	CNELU::Scene->animate ( fdelta);
	cs->anim (fdelta, CNELU::Scene->getCam());
}

// ***************************************************************************

CObjectViewer::CObjectViewer ()
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	_SceneRoot= NULL;
	
	init3d ();
	
	// vl: is it really useful? i moved it from ov.exe init
	CScene::registerBasics ();

	registerSerial3d();

	_MainFrame = NULL;
	_SlotDlg=NULL;
	_AnimationSetDlg=NULL;
	_AnimationDlg=NULL;
	_ParticleDlg = NULL;
	_FontGenerator = NULL;
	_VegetableLandscape= NULL;
	_VegetableCollisionManager= NULL;
	_VegetableCollisionEntity= NULL;
	_CameraFocal = 75.f; // default value for the focal
	_SelectedObject = 0xffffffff;
	_LightGroupDlg = NULL;
	_ChooseFrameDelayDlg = NULL;
	_ChooseBGColorDlg = NULL;
	_DayNightDlg = NULL;
	_WaterPoolDlg = NULL;
	_SoundAnimDlg = NULL;
	_VegetableDlg = NULL;
	_GlobalWindDlg = NULL;
	_SkeletonScaleDlg = NULL;
	_TuneMRMDlg= NULL;


	// no frame delay is the default
	_FrameDelay = 0;

	// Hotspot color
	_HotSpotColor.R=255;
	_HotSpotColor.G=255;
	_HotSpotColor.B=0;
	_HotSpotColor.A=255;

	_BackGroundColor = CRGBA::Black;

	// Hotspot size
	_HotSpotSize=10.f;

	_Wpm = &NL3D::GetWaterPoolManager();

	_GlobalRetriever= NULL;
	_ObjectLightTest= NULL;

	_CharacterScalePos= 1;
	_CurrentCamera = -1;
	_Direct3d = false;
	_Fog = false;
	_FogStart = 0;
	_FogEnd = 1;
	_FogColor = NLMISC::CRGBA::Black;

	_FXUserMatrix.identity();

	_FXMatrixVisible = false;
	_FXUserMatrixVisible = false;
	_SceneMatrixVisible = false;
	_OcclusionTestMeshsVisible = false;
	_CS = NULL;
}

// ***************************************************************************
std::string CObjectViewer::getModulePath() const
{
	// Get the configuration file path (located in same directory as module)
	HMODULE hModule = AfxGetInstanceHandle();
	nlassert(hModule); // shouldn't be null now anymore in any case
	nlassert(hModule != GetModuleHandle(NULL)); // if this is dll, the module handle can't be same as exe
	char sModulePath[256];
	int res = GetModuleFileName(hModule, sModulePath, 256); nlassert(res);
	nldebug("Object viewer module path is '%s'", sModulePath);
	_splitpath (sModulePath, SDrive, SDir, NULL, NULL);
	_makepath (sModulePath, SDrive, SDir, "object_viewer", ".cfg");
	return sModulePath;
}


// ***************************************************************************
void CObjectViewer::loadDriverName()
{
	// Load the config file
	CConfigFile cf;
	cf.load (getModulePath());
	CConfigFile::CVar *var = cf.getVarPtr("driver");
	_Direct3d = var && (var->asString() == "direct3d");
}

// ***************************************************************************
void CObjectViewer::loadConfigFile()
{
	// Load object_viewer.ini
	try
	{
		// Load the config file
		CConfigFile cf;
		cf.load (getModulePath());
		
		try
		{
			// Add search pathes
			CConfigFile::CVar &search_pathes = cf.getVar ("search_pathes");
			for (uint i=0; i<(uint)search_pathes.size(); i++)
				CPath::addSearchPath (search_pathes.asString(i));
		}
		catch(EUnknownVar &)
		{}

		try
		{
			// Add recusrive search pathes
			CConfigFile::CVar &recursive_search_pathes = cf.getVar ("recursive_search_pathes");
			for (uint i=0; i<(uint)recursive_search_pathes.size(); i++)
				CPath::addSearchPath (recursive_search_pathes.asString(i), true, false);
		}
		catch(EUnknownVar &)
		{}

		// Add extension remapping
		try
		{
			CConfigFile::CVar &extensions_remapping = cf.getVar ("extensions_remapping");
			if (extensions_remapping.size()%2 != 0)
			{
				nlwarning ("extensions_remapping must have a multiple of 2 entries (ex: extensions_remapping={\"dds\",\"tga\"};)");
			}
			else
			{
				for (uint i=0; i<(uint)extensions_remapping.size(); i+=2)
					CPath::remapExtension(extensions_remapping.asString(i), extensions_remapping.asString(i+1), true);
			}
		}
		catch (EUnknownVar &)
		{
		}
	
		// debug, display path
		//CPath::display();

		// set the sound banks and sample banks
		try
		{
/*			CConfigFile::CVar &var = cf.getVar("sound_path");
			string soundPath = var.asString();

			var = cf.getVar("soundbanks");
			for (uint i=0; i<(uint)var.size(); i++)
			{
				string dir = soundPath;
				dir.append("/").append(var.asString(i).c_str());
				CSoundSystem::addSoundBank(dir);
			}
*/
			{
				CConfigFile::CVar &var = cf.getVar("sample_path");
				string samplePath(var.asString());
				CSoundSystem::setSamplePath(samplePath);
			}

			{
				CConfigFile::CVar &var = cf.getVar("packed_sheet_path");
				string packedSheetPath(var.asString());
				CSoundSystem::setPackedSheetPath(packedSheetPath);
			}

			/*CConfigFile::CVar var = cf.getVar("samplebanks");
			for (uint i=0; i<(uint)var.size(); i++)
				CSoundSystem::addSampleBank(var.asString(i).c_str());*/
		}
		catch (EUnknownVar &)
		{
			//::MessageBox(NULL, "warning : 'sample_path' or 'packed_sheet_path' variable not defined.\nSound will not work properly.", "Objectviewer.cfg", MB_OK|MB_ICONEXCLAMATION);
		}

		// load the camera focal
		try
		{
			CConfigFile::CVar &camera_focal = cf.getVar("camera_focal");
			_CameraFocal = camera_focal.asFloat();
		}
		catch (EUnknownVar &)
		{
		}


		// load Scene light setup.
		try
		{
			CConfigFile::CVar &var = cf.getVar("scene_light_enabled");
			_SceneLightEnabled = var.asInt() !=0 ;
		}
		catch (EUnknownVar &)
		{
			_SceneLightEnabled= false;
		}
		try
		{
			CConfigFile::CVar &var = cf.getVar("scene_light_sun_ambiant");
			_SceneLightSunAmbiant.R = var.asInt(0);
			_SceneLightSunAmbiant.G = var.asInt(1);
			_SceneLightSunAmbiant.B = var.asInt(2);
		}
		catch (EUnknownVar &)
		{
			_SceneLightSunAmbiant= NLMISC::CRGBA::Black;
		}
		try
		{
			CConfigFile::CVar &var = cf.getVar("scene_light_sun_diffuse");
			_SceneLightSunDiffuse.R = var.asInt(0);
			_SceneLightSunDiffuse.G = var.asInt(1);
			_SceneLightSunDiffuse.B = var.asInt(2);
		}
		catch (EUnknownVar &)
		{
			_SceneLightSunDiffuse= NLMISC::CRGBA::White;
		}
		try
		{
			CConfigFile::CVar &var = cf.getVar("scene_light_sun_specular");
			_SceneLightSunSpecular.R = var.asInt(0);
			_SceneLightSunSpecular.G = var.asInt(1);
			_SceneLightSunSpecular.B = var.asInt(2);
		}
		catch (EUnknownVar &)
		{
			_SceneLightSunSpecular= NLMISC::CRGBA::White;
		}
		try
		{
			CConfigFile::CVar &var = cf.getVar("scene_light_sun_dir");
			_SceneLightSunDir.x = var.asFloat(0);
			_SceneLightSunDir.y = var.asFloat(1);
			_SceneLightSunDir.z = var.asFloat(2);
			_SceneLightSunDir.normalize();
		}
		catch (EUnknownVar &)
		{
			_SceneLightSunDir.set(0, 1, -1);
			_SceneLightSunDir.normalize();
		}
		try
		{
			CConfigFile::CVar &var = cf.getVar("object_light_test");
			_ObjectLightTestShape= var.asString();
		}
		catch (EUnknownVar &)
		{
		}		

		// Load vegetable Landscape cfg.
		loadVegetableLandscapeCfg(cf);

		// load automatfic animations
		try
		{
			CConfigFile::CVar &var = cf.getVar("automatic_animation_path");
			std::auto_ptr<CAnimationSet> as(new CAnimationSet);
			//
			bool loadingOk = as->loadFromFiles(var.asString(),true ,"anim",true);	
			//
			if (!loadingOk)
			{
				//::MessageBox(NULL, "Warning : Unable to load all automatic animation", "Error", MB_OK | MB_ICONEXCLAMATION);
				nlwarning("Unable to load all automatic animation");
			}
			CNELU::Scene->setAutomaticAnimationSet(as.release());
		}
		catch (EUnknownVar &)
		{
			//::MessageBox(NULL, "No automatic animation path specified, please set 'automatic_animation_path'", "warning", MB_OK);
			nlwarning("No automatic animation path specified");
		}

		// load CharacterScalePos
		try
		{
			CConfigFile::CVar &var = cf.getVar("character_scale_pos");
			_CharacterScalePos= var.asFloat();
		}
		catch (EUnknownVar &)
		{
		}		

		// Fog
		CConfigFile::CVar *var = cf.getVarPtr("fog");
		if (var)
			_Fog = var->asInt() != 0;
		var = cf.getVarPtr("fog_start");
		if (var)
			_FogStart = var->asFloat();
		var = cf.getVarPtr("fog_end");
		if (var)
			_FogEnd = var->asFloat();
		var = cf.getVarPtr("fog_color");
		if (var)
			_FogColor = CRGBA ((uint8)(var->asInt(0)), (uint8)(var->asInt(1)), (uint8)(var->asInt(2)));

		// Clouds
		_CSS.NbCloud = 0;
		if (var = cf.getVarPtr("cloud_count"))
			_CSS.NbCloud = var->asInt();
		if (var = cf.getVarPtr("cloud_diffuse"))
		{
			_CSS.Diffuse.R = var->asInt(0);
			_CSS.Diffuse.G = var->asInt(1);
			_CSS.Diffuse.B = var->asInt(2);
		}
		if (var = cf.getVarPtr("cloud_ambient"))
		{
			_CSS.Ambient.R = var->asInt(0);
			_CSS.Ambient.G = var->asInt(1);
			_CSS.Ambient.B = var->asInt(2);
		}
		if (var = cf.getVarPtr("cloud_speed"))
			_CSS.CloudSpeed = var->asFloat();
		if (var = cf.getVarPtr("cloud_update_period"))
			_CSS.TimeToChange = var->asFloat();
		if (var = cf.getVarPtr("cloud_wind_speed"))
			_CSS.WindSpeed = var->asFloat();
	}
	catch (Exception& e)
	{
		::MessageBox (NULL, e.what(), "Objectviewer.cfg", MB_OK|MB_ICONEXCLAMATION);
	}
}


// ***************************************************************************
// properly remove a single window
static void removeWindow(CWnd *wnd)
{
	if (!wnd) return;
	wnd->DestroyWindow();
	delete wnd;
}

// ***************************************************************************
CObjectViewer::~CObjectViewer ()
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());
	removeWindow(_MainFrame);			
	removeWindow(_SlotDlg);	
	removeWindow(_AnimationSetDlg);	
	removeWindow(_AnimationDlg);	
	removeWindow(_DayNightDlg);	
	removeWindow(_WaterPoolDlg);	
	removeWindow(_SoundAnimDlg);	
	removeWindow(_LightGroupDlg);	
	removeWindow(_ChooseBGColorDlg);	
	removeWindow(_VegetableDlg);	
	removeWindow(_GlobalWindDlg);	
	removeWindow(_SkeletonScaleDlg);	
	removeWindow(_TuneMRMDlg);	
	delete _FontGenerator;	
}

// ***************************************************************************

void CObjectViewer::initCamera ()
{
	// Camera
	CFrustum frustrum;
	uint32 width, height;
	CNELU::Driver->getWindowSize (width, height);
	frustrum.initPerspective( _CameraFocal *(float)Pi/180.f, height != 0 ? (float)width/(float)height : 0.f, 0.1f, 1000.f);
	CNELU::Camera->setFrustum (frustrum);

	// Others camera
	uint i;
	for (i=0; i<_Cameras.size (); i++)
	{
		frustrum.initPerspective( _ListInstance[_Cameras[i]]->Camera->getFov(), height != 0 ? (float)width/(float)height : 0.f, 0.1f, 1000.f);
		_ListInstance[_Cameras[i]]->Camera->setFrustum (frustrum);
	}
}

// ***************************************************************************

bool CObjectViewer::initUI (HWND parent)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());	

	// initialize NeL context if needed
	if (!NLMISC::INelContext::isContextInitialised())
		new NLMISC::CApplicationContext;

	// The fonts manager
	_FontManager.setMaxMemory(2000000);

	// The windows path
	uint dSize = ::GetWindowsDirectory(NULL, 0);
	nlverify(dSize);
	char *wd = new char[dSize];	
	nlverify(::GetWindowsDirectory(wd, dSize));
	_FontPath=wd;
	_FontPath+="\\fonts\\arial.ttf";

	// The font generator
	_FontGenerator = new NL3D::CFontGenerator ( _FontPath );
	delete[] wd;

	// The viewport
	CViewport viewport;

	// Create the icon
	HICON hIcon = (HICON)LoadImage(theApp.m_hInstance, MAKEINTRESOURCE(IDI_APP_ICON), IMAGE_ICON,
		16, 16, 0);
	
	// load name of the driver from the config file
	loadDriverName();

	// Create a doomy driver
	IDriver *driver= _Direct3d?CDRU::createD3DDriver():CDRU::createGlDriver();

	// Get parent window
	CWnd parentWnd;
	CWnd *parentWndPtr=NULL;
	if (parent)
	{
		parentWnd.Attach (parent);
		parentWndPtr=&parentWnd;
	}

	// Create the main frame
	_MainFrame = new CMainFrame (this, (winProc) driver->getWindowProc());

	// Read register
	_MainFrame->registerValue (true);

	// Create the window
	_MainFrame->CFrameWnd::Create (AfxRegisterWndClass(0, 0, NULL, hIcon), 
		"NeL object viewer", 0x00cfc000, /*WS_OVERLAPPEDWINDOW,*/ CFrameWnd::rectDefault, parentWndPtr,
		MAKEINTRESOURCE(IDR_OBJECT_VIEWER_MENU), 0x00000300 /*WS_EX_ACCEPTFILES*/ /*|WS_EX_CLIENTEDGE*/);

	// Detach the hwnd
	parentWnd.Detach ();

	// Delete doomy driver
	delete driver;

	// Create a cwnd
	getRegisterWindowState (_MainFrame, REGKEY_OBJ_VIEW_OPENGL_WND, true);
	_MainFrame->ActivateFrame ();
	_MainFrame->ShowWindow (SW_SHOW);
	_MainFrame->UpdateWindow();

	// Context to open a view
	CCreateContext context;
	context.m_pCurrentDoc=NULL;
	context.m_pCurrentFrame=_MainFrame;
	context.m_pLastView=NULL;
	context.m_pNewDocTemplate=NULL;
	context.m_pNewViewClass=RUNTIME_CLASS(CObjView);

	// Create a view
	CObjView *view = (CObjView*)_MainFrame->CreateView (&context);
	view->ShowWindow (SW_SHOW);
	_MainFrame->SetActiveView(view);
	view = (CObjView*)_MainFrame->GetActiveView();
	view->MainFrame = _MainFrame;

	_MainFrame->ShowWindow (SW_SHOW);
	
	// Init NELU
	if (!CNELU::init (640, 480, viewport, 32, true, view->m_hWnd, false, _Direct3d))
	{
		return false;
	}	
	//CNELU::init (640, 480, viewport, 32, true, _MainFrame->m_hWnd);
	CNELU::Scene->setPolygonBalancingMode(CScene::PolygonBalancingClamp);

	// load the config file
	loadConfigFile();

	// Set the fog
	CNELU::Driver->enableFog (_Fog);
	CNELU::Driver->setupFog (_FogStart, _FogEnd, _FogColor);

	// init sound	
	CSoundSystem::initSoundSystem ();

	// Create a root.
	_SceneRoot= (CTransform*)CNELU::Scene->createModel(NL3D::TransformId);

	// Init default lighting seutp.
	setupSceneLightingSystem(_SceneLightEnabled, _SceneLightSunDir, _SceneLightSunAmbiant, _SceneLightSunDiffuse, _SceneLightSunSpecular);

	// Camera
	initCamera ();

	_MainFrame->OnResetCamera();

	// Create animation set dialog
	_AnimationSetDlg=new CAnimationSetDlg (this, _MainFrame);
	_AnimationSetDlg->Create (IDD_ANIMATION_SET);
	getRegisterWindowState (_AnimationSetDlg, REGKEY_OBJ_VIEW_ANIMATION_SET_DLG, false);

	// Create animation set dialog
	_AnimationDlg=new CAnimationDlg (this, _MainFrame);
	_AnimationDlg->Create (IDD_ANIMATION);
	getRegisterWindowState (_AnimationDlg, REGKEY_OBJ_VIEW_ANIMATION_DLG, false);

	// Create the main dialog
	_SlotDlg=new CMainDlg (this, _MainFrame);
	_SlotDlg->Create (IDD_MAIN_DLG);
	getRegisterWindowState (_SlotDlg, REGKEY_OBJ_VIEW_SLOT_DLG, false);

	// Create particle dialog
	_ParticleDlg=new CParticleDlg (this, _MainFrame, _MainFrame, _AnimationDlg);
	_ParticleDlg->Create (IDD_PARTICLE);
	getRegisterWindowState (_ParticleDlg, REGKEY_OBJ_PARTICLE_DLG, false);

	// Create water pool editor dialog
	_WaterPoolDlg = new CWaterPoolEditor(_Wpm, _MainFrame);
	_WaterPoolDlg->Create (IDD_WATER_POOL);
	getRegisterWindowState (_WaterPoolDlg, REGKEY_OBJ_WATERPOOL_DLG, false);

	// Create day night dialog
	_DayNightDlg = new CDayNightDlg (this, _MainFrame);
	_DayNightDlg->Create (IDD_DAYNIGHT);
	getRegisterWindowState (_DayNightDlg, REGKEY_OBJ_DAYNIGHT_DLG, false);

	// Create vegetable dialog
	_VegetableDlg=new CVegetableDlg (this, _MainFrame);
	_VegetableDlg->Create (IDD_VEGETABLE_DLG);
	getRegisterWindowState (_VegetableDlg, REGKEY_OBJ_VIEW_VEGETABLE_DLG, false);

	// Create global wind dialog
	_GlobalWindDlg= new CGlobalWindDlg (this, _MainFrame);
	_GlobalWindDlg->Create(IDD_GLOBAL_WIND);
	getRegisterWindowState (_GlobalWindDlg, REGKEY_OBJ_GLOBAL_WIND_DLG, false);

	// Create sound animation editor dialog
	_SoundAnimDlg = new CSoundAnimDlg(this, _AnimationDlg, _MainFrame);
	_SoundAnimDlg->Create (IDD_SOUND_ANIM_DLG, _MainFrame);
	getRegisterWindowState (_SoundAnimDlg, REGKEY_OBJ_SOUND_ANIM_DLG, false);

	// Create light group editor dialog
	_LightGroupDlg = new CLightGroupFactor(_MainFrame);
	_LightGroupDlg->Create (IDD_LIGHT_GROUP_FACTOR, _MainFrame);
	getRegisterWindowState (_LightGroupDlg, REGKEY_OBJ_LIGHT_GROUP_DLG, false);

	// Create frame delay window
	_ChooseFrameDelayDlg = new CChooseFrameDelay(this, _MainFrame);
	_ChooseFrameDelayDlg->Create(IDD_CHOOSE_FRAME_DELAY, _MainFrame);
	getRegisterWindowState (_ChooseFrameDelayDlg, REGKEY_CHOOSE_FRAME_DELAY_DLG, false);

	// Set backgroupnd color
	setBackGroundColor(_MainFrame->BgColor);

	// Create bg color window (must create after the background color has been set)
	_ChooseBGColorDlg = new CChooseBGColorDlg(this, _MainFrame);
	_ChooseBGColorDlg->Create(IDD_CHOOSE_BG_COLOR, _MainFrame);
	getRegisterWindowState (_ChooseBGColorDlg, REGKEY_CHOOSE_BG_COLOR_DLG, false);

	// Create bg color window (must create after the background color has been set)
	_ChooseSunColorDlg = new CChooseSunColorDlg(CNELU::Scene, _MainFrame);
	_ChooseSunColorDlg->Create(IDD_CHOOSE_SUN_COLOR, _MainFrame);
	getRegisterWindowState (_ChooseSunColorDlg, REGKEY_CHOOSE_SUN_COLOR_DLG, false);

	// Create skeleton scale dlg
	_SkeletonScaleDlg = new CSkeletonScaleDlg(this, _MainFrame);
	_SkeletonScaleDlg->Create(IDD_SKELETON_SCALE_DLG, _MainFrame);
	getRegisterWindowState (_SkeletonScaleDlg, REGKEY_SKELETON_SCALE_DLG, false);

	// Create tune mrm dlg
	_TuneMRMDlg = new CTuneMrmDlg(this, CNELU::Scene, _MainFrame);
	_TuneMRMDlg->Create(IDD_TUNE_MRM_DLG, _MainFrame);
	getRegisterWindowState (_TuneMRMDlg, REGKEY_TUNE_MRM_DLG, false);
	
	
	_MainFrame->update ();
	

	// Set current frame
	setAnimTime (0.f, 100.f);

	// Add mouse listener to event server
	_MouseListener.addToServer(CNELU::EventServer);
		
	CNELU::Driver->activate ();

	// Enable sum of vram
	CNELU::Driver->enableUsedTextureMemorySum ();

	char sModulePath[256];
	// load the scheme bank if one is present		
	CIFile iF;
	::_makepath (sModulePath, SDrive, SDir, "default", ".scb");		
	if (iF.open(sModulePath))
	{
		try
		{
			iF.serial(SchemeManager);
		}
		catch (NLMISC::EStream &e)
		{
			::MessageBox(NULL, ("Unable to load the default scheme bank file : "  + std::string(e.what())).c_str(), "Object Viewer", MB_ICONEXCLAMATION);
		}
	}
	iF.close();
	
	// try to load a default config file for the viewer (for anitmation and particle edition setup)
	::_makepath (sModulePath, SDrive, SDir, "default", ".ovcgf");
	if (iF.open (sModulePath))
	{
		try
		{
			serial (iF);
		}
		catch (Exception& e)
		{
			::MessageBox (NULL, (std::string("error while loading default.ovcgf : ") + e.what()).c_str(), "NeL object viewer", MB_OK|MB_ICONEXCLAMATION);
		}
	}

	// Create the cloud scape
	_CS = new CCloudScape(CNELU::Driver);
	_CS->init (&_CSS);	

	return true;
}

// ***************************************************************************

void CObjectViewer::addTransformation (CMatrix &current, CAnimation *anim, float begin, float end, ITrack *posTrack, ITrack *rotquatTrack, 
									   ITrack *nextPosTrack, ITrack *nextRotquatTrack, bool removeLast)
{
	// In place ?
	if (_AnimationDlg->Inplace)
	{
		// Just identity
		current.identity();
	}
	else
	{
		// Remove the start of the animation
		CQuat rotEnd (0,0,0,1);
		CVector posEnd (0,0,0);
		if (rotquatTrack)
		{
			// Interpolate the rotation
			rotquatTrack->interpolate (end, rotEnd);
		}
		if (posTrack)
		{
			// Interpolate the position
			posTrack->interpolate (end, posEnd);
		}

		// Add the final rotation and position
		CMatrix tmp;
		tmp.identity ();
		tmp.setRot (rotEnd);
		tmp.setPos (posEnd);

		// Incremental ?
		if (_AnimationDlg->IncPos)
			current *= tmp;
		else
			current = tmp;

		if (removeLast)
		{
			CQuat rotStart (0,0,0,1);
			CVector posStart (0,0,0);
			if (nextRotquatTrack)
			{
				// Interpolate the rotation
				nextRotquatTrack->interpolate (begin, rotStart);
			}
			if (nextPosTrack)
			{
				// Interpolate the position
				nextPosTrack->interpolate (begin, posStart);
			}
			// Remove the init rotation and position of the next animation
			tmp.identity ();
			tmp.setRot (rotStart);
			tmp.setPos (posStart);
			tmp.invert ();
			current *= tmp;

			// Normalize the mt
			CVector I = current.getI ();
			CVector J = current.getJ ();
			I.z = 0;
			J.z = 0;
			J.normalize ();
			CVector K = I^J;
			K.normalize ();
			I = J^K;
			I.normalize ();
			tmp.setRot (I, J, K);
			tmp.setPos (current.getPos ());
			current = tmp;
		}
	}
}

// ***************************************************************************

void CObjectViewer::setupPlaylist (float time)
{
	// Update animation dlg

	// Gor each object
	uint i;
	for (i=0; i<_ListInstance.size(); i++)
	{
		// Empty with playlist
		uint j;
		for (j=0; j<CChannelMixer::NumAnimationSlot; j++)
		{
			// Empty slot
			_ListInstance[i]->Playlist.setAnimation (j, CAnimationPlaylist::empty);
		}

		// With channel mixer ?
		if (_AnimationSetDlg->UseMixer)
		{
			// Setup from slots
			_ListInstance[i]->setAnimationPlaylist (getFrameRate ());

			// A playlist
			_ListInstance[i]->Playlist.setupMixer (_ListInstance[i]->ChannelMixer, _AnimationDlg->getTime());
		}
		else
		{			
			// Some animation in the list ?
			if (_ListInstance[i]->Saved.PlayList.size()>0)
			{
				// Index choosed
				uint choosedIndex = 0xffffffff;

				// Track here
				bool there = false;

				// Current matrix
				CMatrix	current;
				current.identity ();

				// Current animation
				CAnimation *anim = NULL;
				ITrack *posTrack = NULL;
				ITrack *rotquatTrack = NULL;

				// Try channel animationset
				anim = _ListInstance[i]->AnimationSet.getAnimation (_ListInstance[i]->AnimationSet.getAnimationIdByName (_ListInstance[i]->Saved.PlayList[0]));
				if (anim)
				{
					posTrack = (ITrack *)anim->getTrackByName ("pos");
					rotquatTrack = (ITrack *)anim->getTrackByName ("rotquat");
				}
				there = posTrack || rotquatTrack;

				// Accumul time
				float startTime=0;
				float endTime=anim->getEndTime()-anim->getBeginTime();

				// Animation index
				uint index = 0;

				// Get animation used in the list
				while (time>=endTime)
				{
					// Next animation
					index++;
					if (index<_ListInstance[i]->Saved.PlayList.size())
					{
						// Pointer on the animation
						CAnimation *newAnim=_ListInstance[i]->AnimationSet.getAnimation (_ListInstance[i]->AnimationSet.getAnimationIdByName(_ListInstance[i]->Saved.PlayList[index]));
						ITrack *newPosTrack = (ITrack *)newAnim->getTrackByName ("pos");
						ITrack *newRotquatTrack = (ITrack *)newAnim->getTrackByName ("rotquat");

						// Add the transformation
						addTransformation (current, anim, newAnim->getBeginTime(), anim->getEndTime(), posTrack, rotquatTrack, newPosTrack, newRotquatTrack, true);

						// Pointer on the animation
						anim = newAnim;
						posTrack = newPosTrack;
						rotquatTrack = newRotquatTrack;

						// Add start time
						startTime = endTime;
						endTime = startTime + anim->getEndTime()-anim->getBeginTime();

					}
					else
					{
						// Add the transformation
						addTransformation (current, anim, 0, anim->getEndTime(), posTrack, rotquatTrack, NULL, NULL, false);

						break;
					}
				}

				// Time cropped ?
				if (index>=_ListInstance[i]->Saved.PlayList.size())
				{
					// Yes
					index--;

					// Good index
					choosedIndex = _ListInstance[i]->AnimationSet.getAnimationIdByName (_ListInstance[i]->Saved.PlayList[index]);
					anim=_ListInstance[i]->AnimationSet.getAnimation (choosedIndex);

					// End time for last anim
					startTime = anim->getEndTime () - time;
				}
				else
				{
					// No

					// Add the transformation
					addTransformation (current, anim, 0, anim->getBeginTime() + time - startTime, posTrack, rotquatTrack, NULL, NULL, false);

					// Good index
					choosedIndex = _ListInstance[i]->AnimationSet.getAnimationIdByName (_ListInstance[i]->Saved.PlayList[index]);				

					// Get the animation
					anim=_ListInstance[i]->AnimationSet.getAnimation (choosedIndex);

					// Final time
					startTime -= anim->getBeginTime ();
				}

				// Set the slot		
				_ListInstance[i]->Playlist.setTimeOrigin (0, startTime);
				_ListInstance[i]->Playlist.setWrapMode (0, CAnimationPlaylist::Clamp);
				_ListInstance[i]->Playlist.setStartWeight (0, 1, 0);
				_ListInstance[i]->Playlist.setEndWeight (0, 1, 1);
				_ListInstance[i]->Playlist.setAnimation (0, choosedIndex);

				// Setup the channel
				_ListInstance[i]->Playlist.setupMixer (_ListInstance[i]->ChannelMixer, _AnimationDlg->getTime());

				// Setup the pos and rot for this shape
				if (there)
				{
					CVector		pos= current.getPos();
					// Get a skeleton model
					CSkeletonModel *skelModel=dynamic_cast<CSkeletonModel*>(_ListInstance[i]->TransformShape);
					// If a  skeleton model
					if(skelModel)
					{
						// scale animated pos value with the CFG scale
						pos*= _CharacterScalePos;
					}

					if (_ListInstance[i]->TransformShape)
					{
						_ListInstance[i]->TransformShape->setPos (pos);
						_ListInstance[i]->TransformShape->setRotQuat (current.getRot());
					}
					if (_ListInstance[i]->Camera)
					{
						_ListInstance[i]->Camera->setPos (pos);
						_ListInstance[i]->Camera->setRotQuat (current.getRot());
					}
				}
			}
			else
			{
				if (_ListInstance[i]->TransformShape)
				{
					CMeshBase *meshBase = dynamic_cast<CMeshBase *> ((IShape*)_ListInstance[i]->TransformShape->Shape);
					if (meshBase)
					{
						_ListInstance[i]->TransformShape->setPos (meshBase->getDefaultPos ()->getDefaultValue ());
						_ListInstance[i]->TransformShape->setRotQuat (meshBase->getDefaultRotQuat ()->getDefaultValue ());
						_ListInstance[i]->TransformShape->setScale (meshBase->getDefaultScale ()->getDefaultValue ());
					}
					else
					{
						/*_ListInstance[i]->TransformShape->setPos (CVector::Null);
						_ListInstance[i]->TransformShape->setRotQuat (CQuat::Identity);
						_ListInstance[i]->TransformShape->setScale (1, 1, 1);*/
					}
				}
				if (_ListInstance[i]->Camera)
				{
					_ListInstance[i]->Camera->setPos (_ListInstance[i]->Camera->getDefaultPos ()->getDefaultValue ());
					_ListInstance[i]->Camera->setTargetPos (_ListInstance[i]->Camera->getDefaultTargetPos ()->getDefaultValue ());
				}
			}
		}
	}
}


// tool funct : this if a window has another window as a parent (recursive)
// if parent == true, this return true
static bool isParentWnd(HWND parent, HWND son)
{
	if (parent == son) return true;
	HWND directParent = GetParent (son);
	if (!directParent) return false;
	if (directParent == parent) return true;
	return isParentWnd(parent, directParent);
}


// ***************************************************************************
void CObjectViewer::go ()
{
	nlassert(!_InstanceRunning); // this shouldn't be called if an instance of the viewer is running.
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	_InstanceRunning = true;
	CGraph graph("ms", 10, 10, 200, 100, CRGBA(64, 64, 64), 20, 200);

	do
	{		
		_CrtCheckMemory();
		if (isParentWnd(_MainFrame->m_hWnd, GetForegroundWindow()))
		{
 			CNELU::Driver->activate ();			

			// Handle animation
			_AnimationDlg->handle ();

			// Handle sound animation
			_SoundAnimDlg->handle ();

			// Handle sound animation
			_LightGroupDlg->handle ();

			// Setup the channel mixer
			_AnimationSetDlg->UpdateData ();

			// Setup the play list
			setupPlaylist (_AnimationDlg->getTime());

			// Eval sound tracks
			evalSoundTrack (_AnimationDlg->getLastTime(), _AnimationDlg->getTime());

			// Animate the automatic animation in the scene
			//CNELU::Scene->animate( (float) + NLMISC::CTime::ticksToSecond( NLMISC::CTime::getPerformanceTime() ) );

			
			// Eval channel mixer for transform
			for (uint i=0; i<_ListInstance.size(); i++)
				
			_ListInstance[i]->ChannelMixer.eval (false);

			animateCNELUScene (_CS);			

			// Clear the buffers
			CNELU::clearBuffers(_BackGroundColor);			

			
			//if (_CS) _CS->setDebugQuad(true);

			// call of callback list
			{
				std::vector<IMainLoopCallBack *> copyVect(_CallBackList.begin(), _CallBackList.end());
				for (std::vector<IMainLoopCallBack *>::iterator it = _CallBackList.begin(); it != _CallBackList.end(); ++it)
				{
					(*it)->goPreRender();
				}
			}
			// Render the CS
			if (_CS) _CS->render ();			
			
			// Draw the scene		
			CNELU::Scene->render();	
			
			// call of callback list
			{
				std::vector<IMainLoopCallBack *> copyVect(_CallBackList.begin(), _CallBackList.end());
				for (std::vector<IMainLoopCallBack *>::iterator it = _CallBackList.begin(); it != _CallBackList.end(); ++it)
				{
					(*it)->goPostRender();
				}
			}
			
			if (_OcclusionTestMeshsVisible)
			{				
				CNELU::Scene->renderOcclusionTestMeshs();
			}

			// Profile polygon count
			CPrimitiveProfile in, out;
			CNELU::Driver->profileRenderedPrimitives (in, out);

			// Draw the hotSpot
			if (_MainFrame->MoveMode == CMainFrame::ObjectMode)
			{
				float radius=_HotSpotSize/2.f;
				CNELU::Driver->setupModelMatrix (CMatrix::Identity);
				CDRU::drawLine (_MouseListener.getHotSpot()+CVector (radius, 0, 0), _MouseListener.getHotSpot()+CVector (-radius, 0, 0), _HotSpotColor, *CNELU::Driver);
				CDRU::drawLine (_MouseListener.getHotSpot()+CVector (0, radius, 0), _MouseListener.getHotSpot()+CVector (0, -radius, 0), _HotSpotColor, *CNELU::Driver);
				CDRU::drawLine (_MouseListener.getHotSpot()+CVector (0, 0, radius), _MouseListener.getHotSpot()+CVector (0, 0, -radius), _HotSpotColor, *CNELU::Driver);
			}

			// Test some keys
			if (CNELU::AsyncListener.isKeyPushed(KeyF3))
			{
				// Change render mode
				switch (CNELU::Driver->getPolygonMode())
				{
				case IDriver::Filled:
					CNELU::Driver->setPolygonMode (IDriver::Line);
					break;
				case IDriver::Line:
					CNELU::Driver->setPolygonMode (IDriver::Point);
					break;
				case IDriver::Point:
					CNELU::Driver->setPolygonMode (IDriver::Filled);
					break;
				}
			}

			// draw various matrix
			if (_FXMatrixVisible) drawFXMatrix();
			if (_FXUserMatrixVisible) drawFXUserMatrix();
			if (_SceneMatrixVisible) drawSceneMatrix();			
			
			// draw Skeleton Scale Dlg selection
			if(_SkeletonScaleDlg)
				_SkeletonScaleDlg->drawSelection();

			// Test Window Keys
			bool	keyWndOk= false;
			if (CNELU::AsyncListener.isKeyPushed(Key1))
				_MainFrame->OnWindowAnimation(), keyWndOk= true;
			if (CNELU::AsyncListener.isKeyPushed(Key2))
				_MainFrame->OnWindowAnimationset(), keyWndOk= true;
			if (CNELU::AsyncListener.isKeyPushed(Key3))
				_MainFrame->OnWindowMixersslots(), keyWndOk= true;
			if (CNELU::AsyncListener.isKeyPushed(Key4))
				_MainFrame->OnWindowParticles(), keyWndOk= true;
			if (CNELU::AsyncListener.isKeyPushed(Key5))
				_MainFrame->OnWindowDayNight(), keyWndOk= true;
			if (CNELU::AsyncListener.isKeyPushed(Key6))
				_MainFrame->OnWindowWaterPool(), keyWndOk= true;
			if (CNELU::AsyncListener.isKeyPushed(Key7))
				_MainFrame->OnWindowVegetable(), keyWndOk= true;
			if (CNELU::AsyncListener.isKeyPushed(Key8))
				_MainFrame->OnWindowGlobalwind(), keyWndOk= true;
			if (CNELU::AsyncListener.isKeyPushed(Key9))
				_MainFrame->OnWindowSoundAnim(), keyWndOk= true;
			if (CNELU::AsyncListener.isKeyPushed(KeyO))
				_MainFrame->OnFileOpen(), keyWndOk= true;

			// Reload texture ?
			if (CNELU::AsyncListener.isKeyPushed(KeyR))
				_MainFrame->OnReloadTextures();

			// If some window activated, reset the focus to the main wnd.
			if(keyWndOk)
				_MainFrame->SetActiveWindow();

			// Calc FPS
			static sint64 lastTime=NLMISC::CTime::getPerformanceTime ();
			sint64 newTime=NLMISC::CTime::getPerformanceTime ();
			float fps = (float)(1.0 / NLMISC::CTime::ticksToSecond (newTime-lastTime));
			lastTime=newTime;
			char msgBar[1024];
			uint nbPlayingSources, nbSources;
			if (CSoundSystem::getAudioMixer())
			{
				nbPlayingSources = CSoundSystem::getAudioMixer()->getUsedTracksCount();
				nbSources = CSoundSystem::getAudioMixer()->getPlayingSourcesCount();
			}
			else
			{
				nbPlayingSources = nbSources = NULL;
			}

			// Display std info.
			sprintf (msgBar, "%s - Nb tri: %d -Texture used (Mo): %5.2f - Texture allocated (Mo): %5.2f - Distance: %5.0f - Sounds: %d/%d - Fps: %03.1f",
				_Direct3d?"Direct3d":"OpenGL",
							in.NLines+in.NPoints+in.NQuads*2+in.NTriangles+in.NTriangleStrips, (float)CNELU::Driver->getUsedTextureMemory () / (float)(1024*1024), 
							(float)CNELU::Driver->profileAllocatedTextureMemory () / (float)(1024*1024), 
							(_SceneCenter-CNELU::Camera->getMatrix().getPos()).norm(),						 
							nbPlayingSources,
							nbSources,
							fps
							);
			// Display
			_MainFrame->StatusBar.SetWindowText (msgBar);

			// Display Vegetable info.
			if(_VegetableDlg!=NULL)
			{
				if(_VegetableLandscape != NULL)
				{
					char vegetMsgBar[1024];
					sprintf (vegetMsgBar, "%d", _VegetableLandscape->Landscape.getNumVegetableFaceRendered());
					_VegetableDlg->StaticPolyCount.SetWindowText(vegetMsgBar);
				}
				else
				{
					_VegetableDlg->StaticPolyCount.SetWindowText("0");
				}
			}

			// graph disabled for now..
			// graph.addValue(CNELU::Scene->getEllapsedTime() * 1000.f);
			// graph.renderGraph();

			// Swap the buffers
			CNELU::swapBuffers();

			// Select the good camera
			if (_MainFrame->MoveMode == CMainFrame::CameraMode)
			{
				sint cameraId = getCurrentCamera ();
				if (cameraId != -1)
				{
					CInstanceInfo *info = getInstance(getCameraInstance (cameraId));
					nlassert (info->Camera);
					CNELU::Scene->setCam (info->Camera);
				}
			}
			else
			{
				CNELU::Scene->setCam (CNELU::Camera);
			}

			if (_MainFrame->MoveMode == CMainFrame::ObjectMode)
			{
				_MouseListener.setMouseMode (CEvent3dMouseListener::edit3d);
			}
			else
			{
				_MouseListener.setMouseMode (CEvent3dMouseListener::firstPerson);
				_MouseListener.setSpeed (_MainFrame->MoveSpeed);
			}

		

			// Reset camera aspect ratio
			initCamera ();

			if (_MainFrame->isMoveElement())
			{
				// for now we apply a transform on the selected object in the particle system			
				_ParticleDlg->moveElement(_MouseListener.getModelMatrix());
			}
			else if (_MainFrame->isMoveFX())
			{
				_ParticleDlg->setPSWorldMatrix(_MouseListener.getModelMatrix());
			}
			else if (_MainFrame->isMoveFXUserMatrix())
			{
				setFXUserMatrix(_MouseListener.getModelMatrix());
			}
			else if (_MainFrame->isMoveObjectLightTest())
			{
				_ObjectLightTestMatrix= _MouseListener.getModelMatrix();
			}
			else if (_MainFrame->isMoveSceneRoot())
			{
				_SceneRoot->setTransformMode (ITransformable::DirectMatrix);
				_SceneRoot->setMatrix (_MouseListener.getModelMatrix());
			}
			else
			{
				nlassert(_MainFrame->isMoveCamera());

				// New matrix from camera
				CNELU::Camera->setTransformMode (ITransformable::DirectMatrix);
				CNELU::Camera->setMatrix (_MouseListener.getViewMatrix());

				// Vegetable: manage collision snapping if wanted and possible
				if(_VegetableSnapToGround && _VegetableLandscape)
				{
					// get matrix from camera.
					CMatrix	matrix= CNELU::Camera->getMatrix();
					// snap To ground.
					CVector	pos= matrix.getPos();
					// if succes to snap to ground
					if(_VegetableCollisionEntity->snapToGround(pos))
					{
						pos.z+= _VegetableSnapHeight;
						matrix.setPos(pos);
						// reset the moveListener and the camera.
						_MouseListener.setMatrix(matrix);
						CNELU::Camera->setMatrix(matrix);
					}
				}
			}


			// Update lighting test Dynamic object position
			if(_ObjectLightTest && _GlobalRetriever)
			{
				// Get the position of the object snapped.
				UGlobalPosition		gPos= _GlobalRetriever->retrievePosition(_ObjectLightTestMatrix.getPos());
				CVector	pos= _GlobalRetriever->getGlobalPosition(gPos);
				_ObjectLightTest->setPos(pos);
				_ObjectLightTest->setRotQuat(_ObjectLightTestMatrix.getRot());

				// Update the matrix and the mouseListener.
				if (_MainFrame->isMoveObjectLightTest())
				{
					_ObjectLightTestMatrix.setPos(pos);
					_MouseListener.setModelMatrix(_ObjectLightTestMatrix);
				}

				// Update the logicInfo so lighting is well computed.
				_ObjectLightTestLogicInfo.GPos= gPos;
			}

			// Pump message from the server
			CNELU::EventServer.pump();

			// Pump others message for the windows
			MSG	msg;
			while ( PeekMessage(&msg, NULL,0,0,PM_REMOVE) )
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			
			CSoundSystem::setListenerMatrix(_MouseListener.getViewMatrix());
			CSoundSystem::poll();

			

			// simulate frame delay
			if (_FrameDelay)
			{
				NLMISC::nlSleep(_FrameDelay);
			}


			// Save last time
			_LastTime=_AnimationDlg->getTime();
			theApp.OnIdle (0);
		}
		else
		{
			// Traditionnal message loop
			MSG	msg;
			while (GetMessage( &msg, NULL, 0, 0) == TRUE)
			{
				::TranslateMessage(&msg);
				::DispatchMessage(&msg);
				if (!IsWindow (_MainFrame->m_hWnd))
					break;

				// Get the foreground window				
				if (isParentWnd(_MainFrame->m_hWnd, GetForegroundWindow()))
					break;
			}
		}		
	}
	while (!CNELU::AsyncListener.isKeyPushed(KeyESCAPE)&&CNELU::Driver->isActive());
	_InstanceRunning = false;
}

// ***************************************************************************

void CObjectViewer::releaseUI ()
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	// Release particles
	removeWindow(_ChooseFrameDelayDlg);	
	removeWindow(_ParticleDlg);	

	// release sound
	CSoundSystem::releaseSoundSystem();

	if (CNELU::Driver->isActive())
	{
		// register window position
		if (CNELU::Driver->getDisplay())
		{
			setRegisterWindowState (_MainFrame, REGKEY_OBJ_VIEW_OPENGL_WND);
		}
	}

	// Write register
	if (_MainFrame)
	{	
		_MainFrame->registerValue (false);
		// Remove the main frame
		if (::IsWindow(*_MainFrame))
		{
			_MainFrame->DestroyWindow();
		}
		delete _MainFrame;
		_MainFrame = NULL;
	}

	// Release the emitter from the server
	_MouseListener.removeFromServer (CNELU::EventServer);

	// exit

	// remove first possibly created collisions objects.
	if(_VegetableCollisionEntity)
	{
		_VegetableCollisionManager->deleteEntity(_VegetableCollisionEntity);
		_VegetableCollisionEntity= NULL;
	}
	if(_VegetableCollisionManager)
	{
		delete _VegetableCollisionManager;
		_VegetableCollisionManager= NULL;
	}

	// delete Landscape
	if(_VegetableLandscape)
	{
		CNELU::Scene->deleteModel(_VegetableLandscape);
		_VegetableLandscape= NULL;
	}

	

	// Release all instances and all Igs.
	removeAllInstancesFromScene();

	// Remove

	// Create the cloud scape
	delete _CS;
	_CS = NULL;

	// release Root
	CNELU::Scene->deleteModel(_SceneRoot);
	_SceneRoot= NULL;

	// release other 3D.
	CNELU::release();

	
}

// ***************************************************************************

void setRegisterWindowState (const CWnd *pWnd, const char* keyName)
{
	HKEY hKey;
	if (RegCreateKey(HKEY_CURRENT_USER, keyName, &hKey)==ERROR_SUCCESS)
	{
		RECT rect;
		pWnd->GetWindowRect (&rect);
		RegSetValueEx(hKey, "Left", 0, REG_DWORD, (LPBYTE)&rect.left, 4);
		RegSetValueEx(hKey, "Right", 0, REG_DWORD, (LPBYTE)&rect.right, 4);
		RegSetValueEx(hKey, "Top", 0, REG_DWORD, (LPBYTE)&rect.top, 4);
		RegSetValueEx(hKey, "Bottom", 0, REG_DWORD, (LPBYTE)&rect.bottom, 4);
	}
}

// ***************************************************************************

void getRegisterWindowState (CWnd *pWnd, const char* keyName, bool resize)
{
	HKEY hKey;
	if (RegOpenKeyEx(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &hKey)==ERROR_SUCCESS)
	{
		DWORD len=4;
		DWORD type;
		RECT rect;
		RegQueryValueEx (hKey, "Left", 0, &type, (LPBYTE)&rect.left, &len);
		RegQueryValueEx (hKey, "Right", 0, &type, (LPBYTE)&rect.right, &len);
		RegQueryValueEx (hKey, "Top", 0, &type, (LPBYTE)&rect.top, &len);
		RegQueryValueEx (hKey, "Bottom", 0, &type, (LPBYTE)&rect.bottom, &len);

		// Set window pos
		pWnd->SetWindowPos (NULL, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, SWP_NOOWNERZORDER|SWP_NOZORDER|
			(resize?0:SWP_NOSIZE));
	}
}

// ***************************************************************************

void CObjectViewer::setAnimTime (float animStart, float animEnd)
{
	// Dispatch the command
	_SlotDlg->setAnimTime (animStart, animEnd);
	_AnimationDlg->setAnimTime (animStart, animEnd);
	_SoundAnimDlg->setAnimTime (animStart, animEnd);
}

// ***************************************************************************


void CObjectViewer::resetSlots (uint instance)
{
	// Reset the animation set
	_ListInstance[instance]->AnimationSet.reset ();

	// Set no animation in slot UI
	for (uint j=0; j<NL3D::CChannelMixer::NumAnimationSlot; j++)
		_ListInstance[instance]->Saved.SlotInfo[j].Animation = "";

	// Reset the animation list
	_ListInstance[instance]->Saved.AnimationFileName.clear ();

	// Reset the play list
	_ListInstance[instance]->Saved.PlayList.clear ();

	// Reset the skeleton list
	_ListInstance[instance]->Saved.SWTFileName.clear ();

	// Update 
	_AnimationSetDlg->refresh (TRUE);
	_SlotDlg->refresh (TRUE);
	_SoundAnimDlg->refresh (TRUE);
}

// ***************************************************************************

void CObjectViewer::reinitChannels ()
{
	// Add all the instance in the channel mixer
	for (uint i=0; i<_ListInstance.size(); i++)
	{
		// Reset the channels
		_ListInstance[i]->ChannelMixer.resetChannels ();

		// Setup animation set
		_ListInstance[i]->ChannelMixer.setAnimationSet (&(_ListInstance[i]->AnimationSet));

		// Register the transform (but not if it has automatic animation, as a channel mixer has been created for us)
		bool autoAnim = false;
		if (dynamic_cast<CMeshBaseInstance *>(_ListInstance[i]->TransformShape))
		{
			CMeshBase *mb = NLMISC::safe_cast<CMeshBase *>( (IShape *) static_cast<CMeshBaseInstance *>(_ListInstance[i]->TransformShape)->Shape );
			autoAnim = mb->getAutoAnim();
		}

		if (!autoAnim)
		{
			if (_ListInstance[i]->TransformShape)
				_ListInstance[i]->TransformShape->registerToChannelMixer (&(_ListInstance[i]->ChannelMixer), "");
			if (_ListInstance[i]->Camera)
				_ListInstance[i]->Camera->registerToChannelMixer (&(_ListInstance[i]->ChannelMixer), "");
		}
	}

	// Enable / disable channels
	enableChannels ();
}

// ***************************************************************************

void CObjectViewer::enableChannels ()
{
	// Disable some channels
	_AnimationSetDlg->UpdateData ();
	bool enable = (_AnimationSetDlg->UseMixer == 1);

	// Add all the instance in the channel mixer
	for (uint i=0; i<_ListInstance.size(); i++)
	{
		// Get the pos and rot channel id
		uint posId = _ListInstance[i]->AnimationSet.getChannelIdByName ("pos");
		uint rotQuatId = _ListInstance[i]->AnimationSet.getChannelIdByName ("rotquat");
		uint rotEulerId = _ListInstance[i]->AnimationSet.getChannelIdByName ("roteuler");

		if (posId != CAnimationSet::NotFound)
			_ListInstance[i]->ChannelMixer.enableChannel (posId, enable);
		if (rotQuatId != CAnimationSet::NotFound)
			_ListInstance[i]->ChannelMixer.enableChannel (rotQuatId, enable);
		if (rotEulerId != CAnimationSet::NotFound)
			_ListInstance[i]->ChannelMixer.enableChannel (rotEulerId, enable);
	}
}

// ***************************************************************************

float CObjectViewer::getFrameRate ()
{
	return _AnimationDlg->Speed;
}

// ***************************************************************************

string getFilename (const string &file)
{
	// if the direct file exist, return it
	if (NLMISC::CFile::fileExists(file))
		return file;

	string path = NLMISC::CFile::getFilename(file);
	path = CPath::lookup (path, false, false, false);
	if (path.empty())
		path = file;
	return path;
}

// ***************************************************************************

void CObjectViewer::serial (NLMISC::IStream& f)
{
	// version 4: include particle workspace infos
	// serial "OBJV_CFG"
	f.serialCheck ((uint32)'VJBO');
	f.serialCheck ((uint32)'GFC_');

	// serial the version
	int ver=f.serialVersion (4);
	if (ver>=4)
	{
		f.serial(ParticleWorkspaceFilename);
	}
	if (ver>=3)
	{
		// Read the configuration file
		if (f.isReading())
		{
			if (ver <=3)
			{			
				ParticleWorkspaceFilename = "";
			}
			// First instance
			uint firstInstance = (uint)_ListInstance.size();

			// Read information
			std::vector<CInstanceSave> readed;
			f.serialCont (readed);

			// Merge
			for (uint i=0; i<readed.size(); i++)
			{
				try
				{
					// Instance loaded
					uint instance = 0xffffffff;

					if (readed[i].Camera)
					{
						instance = addCamera (readed[i].CameraInfo, readed[i].ShapeFilename.c_str());
					}
					else
					{
						// Load the shape
						CIFile input;
						string path = getFilename (readed[i].ShapeFilename);
						if (input.open (path))
						{
							// Serial a shape
							CShapeStream serialShape;
							serialShape.serial (input);

							// Is a skeleton ?
							if (readed[i].IsSkeleton)
							{
								// Add the skel
								instance = addSkel (serialShape.getShapePointer(), readed[i].ShapeFilename.c_str());
								SkeletonUsedForSound = instance;
							}
							else
							{
								// Add the mesh
								if (readed[i].SkeletonId != 0xffffffff)
									instance = addMesh (serialShape.getShapePointer(), readed[i].ShapeFilename.c_str(), readed[i].SkeletonId + firstInstance, (readed[i].BindBoneName=="")?NULL:readed[i].BindBoneName.c_str());
								else
									instance = addMesh (serialShape.getShapePointer(), readed[i].ShapeFilename.c_str(), 0xffffffff, (readed[i].BindBoneName=="")?NULL:readed[i].BindBoneName.c_str());
							}
						}
						else
						{
							// Error message
							char message[512];
							smprintf (message, 512, "File not found %s", readed[i].ShapeFilename.c_str());
							_MainFrame->MessageBox (message, "NeL object viewer", MB_OK|MB_ICONEXCLAMATION);

							// Stop loading
							break;
						}
					}

					// Check instance number
 					nlassert (instance == (firstInstance+i));

					// Load animations
					for (uint anim=0; anim<readed[i].AnimationFileName.size(); anim++)
					{
						string path = getFilename (readed[i].AnimationFileName[anim]);
						loadAnimation (path.c_str(), instance);
					}

					// Load SWT
					for (uint swt=0; swt<readed[i].SWTFileName.size(); swt++)
					{
						string path = getFilename (readed[i].SWTFileName[swt]);
						loadSWT (path.c_str(), instance);
					}

					// Set the playlist
					_ListInstance[instance]->Saved.PlayList = readed[i].PlayList;

					// Set the slot information
					for (uint slot=0; slot<NL3D::CChannelMixer::NumAnimationSlot; slot++)
						_ListInstance[instance]->Saved.SlotInfo[slot] = readed[i].SlotInfo[slot];
				}
				catch (Exception &e)
				{
					// Error message
					char message[512];
					smprintf (message, 512, "Error loading shape %s: %s", readed[i].ShapeFilename.c_str(), e.what());
					_MainFrame->MessageBox (message, "NeL object viewer", MB_OK|MB_ICONEXCLAMATION);

					// Stop loading
					break;
				}
			}

			// Init channels
			reinitChannels ();

			// Read the selection
			uint32 selection;
			f.serial (selection);
			if (selection+firstInstance < _ListInstance.size())
			{
				_SelectedObject = selection+firstInstance;
			}

			// Invalidate dialogs
			_AnimationSetDlg->refresh (TRUE);
			_SlotDlg->refresh (TRUE);
			_SoundAnimDlg->refresh (TRUE);
		}
		else
		{
			// Build information
			std::vector<CInstanceSave> readed (_ListInstance.size());
			for (uint instance=0; instance<_ListInstance.size(); instance++)
			{
				// Copy the save information
				readed[instance] = _ListInstance[instance]->Saved;
			}

			// Save the configuration
			f.serialCont (readed);

			// Write the selection
			f.serial (_SelectedObject);
		}
	}
}

// ***************************************************************************

bool CObjectViewer::loadInstanceGroup(const char *igFilename)
{
	//AFX_MANAGE_STATE(AfxGetStaticModuleState());

	// Add to the path
	char drive[256];
	char dir[256];
	char path[256];

	// Add search path for the mesh
	_splitpath (igFilename, drive, dir, NULL, NULL);
	_makepath (path, drive, dir, NULL, NULL);
	CPath::addSearchPath (path);

	
	// Open a file
	CIFile file;
	if (file.open (igFilename))
	{		
		// Shape pointer
		NL3D::CInstanceGroup	*ig= new NL3D::CInstanceGroup;	

		try
		{
			// Stream it
			file.serial(*ig);

			// Append the ig.
			addInstanceGroup(ig);
		}
		catch (Exception& e)
		{
			// clean
			delete ig;
			_MainFrame->MessageBox (e.what(), "NeL object viewer", MB_OK|MB_ICONEXCLAMATION);
			return false;
		}
	}
	else
	{
		// Create a message
		char msg[512];
		_snprintf (msg, 512, "Can't open the file %s for reading.", igFilename);
		_MainFrame->MessageBox (msg, "NeL object viewer", MB_OK|MB_ICONEXCLAMATION);
		return false;
	}

	

	return true;		
}



// ***************************************************************************

bool CObjectViewer::loadMesh (std::vector<std::string> &meshFilename, const char* skeleton)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	// Add to the path
	char drive[256];
	char dir[256];
	char path[256];

	// Add search path for the skeleton
	if (skeleton)
	{
		_splitpath (skeleton, drive, dir, NULL, NULL);
		_makepath (path, drive, dir, NULL, NULL);
		CPath::addSearchPath (path);
	}

	// Open a file
	CIFile file;

	// Shape pointer
	IShape *shapeSkel=NULL;
	uint skelIndex = 0xffffffff;
	NL3D::CSkeletonModel *transformSkel=NULL;

	// Skel error ?
	bool skelError=false;

	// Continue ?
	if (skeleton&&(strcmp (skeleton, "")!=0))
	{

		// Open a file
		if (file.open (skeleton))
		{
			// Sream a shape
			CShapeStream streamShape;
			try
			{
				// Stream it
				streamShape.serial (file);

				// Add the shape
				shapeSkel=streamShape.getShapePointer();
			}
			catch (Exception& e)
			{
				_MainFrame->MessageBox (e.what(), "NeL object viewer", MB_OK|MB_ICONEXCLAMATION);

				// error
				skelError=true;
			}
		}
		else
		{
			// Create a message
			char msg[512];
			_snprintf (msg, 512, "Can't open the file %s for reading.", meshFilename);
			_MainFrame->MessageBox (msg, "NeL object viewer", MB_OK|MB_ICONEXCLAMATION);

			// error
			skelError=true;
		}
	}

	// Skeleton error ?
	if (skelError)
		return false;

	// Skeleton used ?
	bool skelUsed = false;

	// Index of the shape
	uint lastShape = 0xffffffff;

	// For each meshes
	for (uint i=0; i<meshFilename.size(); i++)
	{
		// Filename
		const char *fileName = meshFilename[i].c_str();

		// Add search path for the mesh
		_splitpath (fileName, drive, dir, NULL, NULL);
		_makepath (path, drive, dir, NULL, NULL);
		CPath::addSearchPath (path);

		// Shape pointer
		IShape *shapeMesh=NULL;

		if (file.open (fileName))
		{
			// Sream a shape
			CShapeStream streamShape;
			try
			{
				// Stream it
				streamShape.serial (file);

				// Add the shape
				shapeMesh=streamShape.getShapePointer();
			}
			catch (Exception& e)
			{
				_MainFrame->MessageBox (e.what(), "NeL object viewer", MB_OK|MB_ICONEXCLAMATION);
				continue;
			}
		}
		else
		{
			// Create a message
			char msg[512];
			_snprintf (msg, 512, "Can't open the file %s for reading.", fileName);
			_MainFrame->MessageBox (msg, "NeL object viewer", MB_OK|MB_ICONEXCLAMATION);
			continue;
		}

		// Add the skel shape
		if (shapeSkel&&(!skelUsed))
		{
			// Add the skel
			skelIndex = addSkel (shapeSkel, skeleton);
			if (skelIndex != 0xffffffff)
			{
				skelUsed = true;
				transformSkel = dynamic_cast<CSkeletonModel*>(_ListInstance[skelIndex]->TransformShape);
				nlassert (transformSkel);
			}
		}

		// Add the skel shape
		if (shapeMesh)
		{
			// Get the object name
			lastShape = addMesh (shapeMesh, fileName, skelIndex);
		}
	}

	// Skel not used ?
	if ((!skelUsed)&&shapeSkel)
	{
		// Remove it
		delete shapeSkel;
	}

	// Select the skeleton
	if (skelIndex != 0xffffffff)
		_SelectedObject = skelIndex;
	else if (lastShape != 0xffffffff)
		_SelectedObject = lastShape;

	// Update windows
	_AnimationSetDlg->refresh (TRUE);
	_SlotDlg->refresh (TRUE);
	_SoundAnimDlg->refresh (TRUE);

	return true;
}

// ***************************************************************************

void CObjectViewer::resetCamera ()
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	_MainFrame->OnResetCamera();
}

// ***************************************************************************

uint CObjectViewer::addMesh (NL3D::IShape* pMeshShape, const char* meshName, uint skelIndex, const char* bindSkelName, bool createInstance)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	// *** Add the shape

	// Store the shape pointer
	if (CNELU::ShapeBank->getPresentState(meshName)!=CShapeBank::NotPresent)
	{
		delete pMeshShape;
	}
	else
		CNELU::ShapeBank->add (meshName, CSmartPtr<IShape> (pMeshShape));

	// Must create the instance?
	if(createInstance)
	{
		// Create a model and add it to the scene
		CTransformShape	*pTrShape=CNELU::Scene->createInstance (meshName);
		nlassert (pTrShape);

		// link to the root for manipulation
		_SceneRoot->hrcLinkSon(pTrShape);

		// Get the real shape used by the instance.
		pMeshShape= pTrShape->Shape;

		// Set the rot model
		if (_MainFrame->Euler)
			pTrShape->setTransformMode (ITransformable::RotEuler);
		else
			pTrShape->setTransformMode (ITransformable::RotQuat);

		// Store the transform shape pointer
		CInstanceInfo *iInfo = new CInstanceInfo;
		iInfo->TransformShape= pTrShape;

		// Store the name of the shape
		iInfo->MustDelete = true;
		iInfo->Saved.ShapeFilename = meshName;
		iInfo->Saved.SkeletonId = skelIndex;
		_ListInstance.push_back (iInfo);	

		// *** Bind to the skeleton

		// Get a mesh instance
		CMeshBaseInstance  *meshInstance=dynamic_cast<CMeshBaseInstance*>(pTrShape);

		// Bind the mesh
		if (skelIndex != 0xffffffff)
		{
			// Get the skeleton
			NL3D::CSkeletonModel *transformSkel = dynamic_cast<CSkeletonModel*>(_ListInstance[skelIndex]->TransformShape);
			nlassert (transformSkel);
			
			// It is a skinned mesh ?
			CMesh *mesh = dynamic_cast<CMesh *>(pMeshShape);
			CMeshMRM *meshMrm = dynamic_cast<CMeshMRM *>(pMeshShape);
			CMeshMRMSkinned *meshMrmSkinned = dynamic_cast<CMeshMRMSkinned *>(pMeshShape);
			if ( (mesh && mesh->getMeshGeom().isSkinned()) || 
				(meshMrm && meshMrm->getMeshGeom().isSkinned()) ||
				meshMrmSkinned)
			{
				// Bind to skeleton
				transformSkel->bindSkin (meshInstance);
			}
			else
			{
				// Bind bone name
				uint bindBone = 0xffffffff;
				std::string boneName = "";

				// Name is passed, look for bone 
				if (bindSkelName)
				{
					// Make a list of bones
					uint bone;
					for (bone=0; bone<transformSkel->Bones.size(); bone++)
					{
						if (transformSkel->Bones[bone].getBoneName() == bindSkelName)
						{
							bindBone = bone;
							boneName = bindSkelName;
							break;
						}
					}
				}

				// Found ? No, look for a bind bone
				if (bindBone == 0xffffffff)
				{
					// Make a list of bones
					vector<string> listBones;
					uint bone;
					for (bone=0; bone<transformSkel->Bones.size(); bone++)
						listBones.push_back (transformSkel->Bones[bone].getBoneName());

					// Get name of the mesh
					char nameMesh[512];
					_splitpath (meshName, NULL, NULL, nameMesh, NULL);

					// Select a bones
					std::string message = "Select a bone to stick " + string (nameMesh);
					CSelectString dialogSelect (listBones, message.c_str(), _MainFrame, false);
					if (dialogSelect.DoModal ()==IDOK)
					{
						// Select your bones
						bindBone = dialogSelect.Selection;
						boneName = listBones[dialogSelect.Selection];
					}
				}

				// Selected ?
				if (bindBone != 0xffffffff)
				{
					transformSkel->stickObject (pTrShape, bindBone);
					/*meshInstance->setPos (CVector::Null);
					meshInstance->setRotQuat (CQuat::Identity);
					meshInstance->setScale (1, 1, 1);*/
				}

				// Set the bone name
				iInfo->Saved.BindBoneName = boneName;
			}
		}

		// Return the instance index
		return (uint)_ListInstance.size()-1;
	}
	else
		return 0xffffffff;
}


// ***************************************************************************
bool CObjectViewer::chooseBone(const std::string &caption, NL3D::CSkeletonModel *&skel, uint &boneIndex, std::string *skelName /*= NULL*/, std::string *boneName /*= NULL*/)
{
	for(uint k = 0; k < _ListInstance.size(); ++k)
	{
		NL3D::CSkeletonModel *transformSkel = dynamic_cast<CSkeletonModel*>(_ListInstance[k]->TransformShape);
		if (transformSkel)
		{
			// Make a list of bones
			vector<string> listBones;
			uint bone;
			for (bone=0; bone<transformSkel->Bones.size(); bone++)
			{
				listBones.push_back (transformSkel->Bones[bone].getBoneName());
			}
			CSelectString dialogSelect (listBones, caption.c_str(), _MainFrame, false);
			if (dialogSelect.DoModal ()==IDOK)
			{
				boneIndex = dialogSelect.Selection; 
				skel = transformSkel;
				if (skelName)
				{
					*skelName = _ListInstance[k]->Saved.ShapeFilename;
				}
				if (boneName)
				{
					*boneName = safe_cast<NL3D::CSkeletonModel *>(_ListInstance[k]->TransformShape)->Bones[boneIndex].getBoneName();
				}
				return true;
			}
			return false;			
		}
	}
	return false;
}

// ***************************************************************************
bool CObjectViewer::isSkeletonPresent() const
{
	for(uint k = 0; k < _ListInstance.size(); ++k)
	{
		NL3D::CSkeletonModel *transformSkel = dynamic_cast<CSkeletonModel*>(_ListInstance[k]->TransformShape);
		if (transformSkel) return true;
	}
	return false;
}

// ***************************************************************************

uint CObjectViewer::addCamera (const NL3D::CCameraInfo &cameraInfo, const char* cameraName)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	// *** Add the shape

	// link to the root for manipulation
	CCamera *pCamera = (CCamera*)CNELU::Scene->createModel (CameraId);
	_SceneRoot->hrcLinkSon(pCamera);

	// Build the camera
	pCamera->build (cameraInfo);

	// Store the transform shape pointer
	CInstanceInfo *iInfo = new CInstanceInfo;
	iInfo->Camera = pCamera;

	// Store the name of the shape
	iInfo->MustDelete = true;
	iInfo->Saved.ShapeFilename = cameraName;
	iInfo->Saved.SkeletonId = 0xffffffff;
	iInfo->Saved.CameraInfo = cameraInfo;
	iInfo->Saved.Camera = true;
	_ListInstance.push_back (iInfo);
	_Cameras.push_back ((uint)_ListInstance.size()-1);

	// Reinit camera
	initCamera ();

	// Return the instance index
	return (uint)_ListInstance.size()-1;
}

// ***************************************************************************

uint CObjectViewer::addSkel (NL3D::IShape* pSkelShape, const char* skelName)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	// *** Add the shape

	// Store the shape pointer
	if (CNELU::ShapeBank->getPresentState(skelName)!=CShapeBank::NotPresent)
	{
		delete pSkelShape;
	}
	else
		CNELU::ShapeBank->add (skelName, CSmartPtr<IShape> (pSkelShape));

	// Create a model and add it to the scene
	CTransformShape	*pTrShape=CNELU::Scene->createInstance (skelName);
	nlassert (pTrShape);

	// link to the root for manipulation
	_SceneRoot->hrcLinkSon(pTrShape);

	// Get the real shape used by the instance.
	pSkelShape= pTrShape->Shape;

	// Get a skeleton model
	CSkeletonModel *skelModel=dynamic_cast<CSkeletonModel*>(pTrShape);

	// Is a skel ?
	if (skelModel)
	{
		// Set the rot model
		if (_MainFrame->Euler)
			pTrShape->setTransformMode (ITransformable::RotEuler);
		else
			pTrShape->setTransformMode (ITransformable::RotQuat);

		// Store the transform shape pointer
		CInstanceInfo *iInfo = new CInstanceInfo;
		iInfo->TransformShape = skelModel;

		// Store the name of the shape
		iInfo->MustDelete = true;
		iInfo->Saved.ShapeFilename = skelName;
		iInfo->Saved.IsSkeleton = true;
		iInfo->Saved.SkeletonId = 0xffffffff;
		_ListInstance.push_back (iInfo);	

		// set this skeleton for Skeleton Scale edition
		_SkeletonScaleDlg->setSkeletonToEdit(skelModel, skelName);

		// Return the instance
		return (uint)_ListInstance.size()-1;
	}
	return 0xffffffff;
}

// ***************************************************************************

IObjectViewer* IObjectViewer::getInterface (int version)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	// Check version number
	if (version!=OBJECT_VIEWER_VERSION)
	{
		MessageBox (NULL, "Bad version of object_viewer.dll.", "NeL object viewer", MB_ICONEXCLAMATION|MB_OK);
		return NULL;
	}
	else
		return new CObjectViewer;
}

// ***************************************************************************

void IObjectViewer::releaseInterface (IObjectViewer* view)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	delete view;
}

// ***************************************************************************

void CObjectViewer::setSingleAnimation (NL3D::CAnimation* pAnim, const char* name, uint instance)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	if (instance < _ListInstance.size())
	{
		// Set active 
		_SelectedObject = instance;

		// Add the animation
		addAnimation (pAnim, (name+std::string(".anim")).c_str(), name, instance);

		// Add the animation to the animationSet
		_AnimationSetDlg->UpdateData (TRUE);
		_AnimationSetDlg->UseMixer = 1;
		_AnimationSetDlg->UpdateData (FALSE);

		// Set the animation in the first slot
		_ListInstance[instance]->Saved.SlotInfo[0].Animation = name;
		_ListInstance[instance]->Saved.SlotInfo[0].Skeleton = "";
		_ListInstance[instance]->Saved.SlotInfo[0].Offset = 0;
		_ListInstance[instance]->Saved.SlotInfo[0].StartTime = (int)(pAnim->getBeginTime()*_AnimationDlg->Speed);
		_ListInstance[instance]->Saved.SlotInfo[0].EndTime = (int)(pAnim->getEndTime()*_AnimationDlg->Speed);
		_ListInstance[instance]->Saved.SlotInfo[0].StartBlend = 1.f;
		_ListInstance[instance]->Saved.SlotInfo[0].EndBlend = 1.f;
		_ListInstance[instance]->Saved.SlotInfo[0].Enable = true;
		for (uint i=1; i<CChannelMixer::NumAnimationSlot; i++)
			_ListInstance[instance]->Saved.SlotInfo[i].Enable = false;

		// Update dialog box
		_AnimationSetDlg->refresh (TRUE);
		_SlotDlg->refresh (TRUE);
		_SoundAnimDlg->refresh(TRUE);

		// Reinit
		reinitChannels ();
	}
}

// ***************************************************************************

void CObjectViewer::setAutoAnimation (NL3D::CAnimationSet* pAnimSet)
{
	CNELU::Scene->setAutomaticAnimationSet (pAnimSet);
}

// ***************************************************************************

void CObjectViewer::setAmbientColor (const NLMISC::CRGBA& color)
{
	CNELU::Driver->setAmbientColor (color);

	// Setup also Scene lighting system here, even if not used.
	CNELU::Scene->setAmbientGlobal(color);
}

// ***************************************************************************

void CObjectViewer::setLight (unsigned char id, const NL3D::CLight& light)
{
	CNELU::Driver->enableLight (id);
	CNELU::Driver->setLight (id, light);
}

// ***************************************************************************


/** add an object that will be notified each time a frame is processed
  * \see removeMainLoopCallBack()
  */
void CObjectViewer::registerMainLoopCallBack(IMainLoopCallBack *i)
{
//	nlassert(std::find(_CallBackList.begin(), _CallBackList.end(), i) == _CallBackList.begin()); // the object was register twice !!
	_CallBackList.push_back(i);
}

/// remove an object that was registered with registerMainLoopCallBack()
void CObjectViewer::removeMainLoopCallBack(IMainLoopCallBack *i)
{
	std::vector<IMainLoopCallBack *>::iterator it = std::find(_CallBackList.begin(), _CallBackList.end(), i);
	nlassert(it  != _CallBackList.end()); // this object wasn't registered
	_CallBackList.erase(it);	
}

// ***************************************************************************
void CObjectViewer::activateTextureSet(uint index)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	std::vector<CInstanceInfo*>::iterator it;
	for (it = _ListInstance.begin(); it != _ListInstance.end(); ++it)
	{
		NL3D::CTransformShape	*trShape= (*it)->TransformShape;
		if (dynamic_cast<NL3D::CMeshBaseInstance *>(trShape))
		{
			static_cast<NL3D::CMeshBaseInstance *>(trShape)->selectTextureSet(index);
		}		
	}	
}

// ***************************************************************************
void CObjectViewer::shuffleTextureSet()
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	std::vector<CInstanceInfo*>::iterator it;
	for (it = _ListInstance.begin(); it != _ListInstance.end(); ++it)
	{
		NL3D::CTransformShape	*trShape= (*it)->TransformShape;
		if (dynamic_cast<NL3D::CMeshBaseInstance *>(trShape))
		{
			static_cast<NL3D::CMeshBaseInstance *>(trShape)->selectTextureSet(rand() % 8);
		}		
	}	
}


// ***************************************************************************

void CObjectViewer::removeAllInstancesFromScene()
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	// Remove all stand alone TransformShapes.
	for(uint instance=0; instance<_ListInstance.size(); instance++)
	{
		delete _ListInstance[instance];
	}

	// Remove all stand alone TransformShapes.
	_ListInstance.clear();
	_Cameras.clear();
	_CurrentCamera = -1;
	_SelectedObject = 0xffffffff;

	// Remove added/loaded igs and their instances.
	for(uint igId=0; igId<_ListIG.size(); igId++)
	{
		// remove instances.
		_ListIG[igId]->removeFromScene(*CNELU::Scene);
		// free up the ig.
		delete _ListIG[igId];
	}
	_ListIG.clear();

	// clear dynamic lighting test
	_GlobalRetriever= NULL;
	CNELU::Scene->deleteInstance(_ObjectLightTest);
	_ObjectLightTest= NULL;

	// Reset mesh cache
	CNELU::ShapeBank->reset();

	// Invalidate dialogs
	if (CNELU::Driver->isActive())
	{
		_AnimationSetDlg->refresh (TRUE);
		_SlotDlg->refresh (TRUE);
		_SoundAnimDlg->refresh (TRUE);
	}

	// remove any skeleton edited
	if(_InstanceRunning)
		_SkeletonScaleDlg->setSkeletonToEdit(NULL, "");
}


// ***************************************************************************
void CObjectViewer::enableFXs(bool enabled)
{
	// Stand alone fxs
	for(uint instance=0; instance<_ListInstance.size(); instance++)
	{
		NL3D::CSegRemanence *sr = dynamic_cast<NL3D::CSegRemanence *>(_ListInstance[instance]->TransformShape);
		if (sr)
		{
			if (enabled) sr->start();
			else sr->stop();
		}
	}
	// remanences in igs
	for(uint igId = 0; igId < _ListIG.size(); ++igId)
	{
		for(uint k = 0; k < _ListIG[igId]->_Instances.size(); ++k)
		{
			NL3D::CSegRemanence *sr = dynamic_cast<NL3D::CSegRemanence *>(_ListIG[igId]->_Instances[k]);
			if (sr)
			{
				if (enabled) sr->start();
				else sr->stop();
			}
		}	
	}	
}

// ***************************************************************************

void CObjectViewer::evalSoundTrack (float lastTime, float currentTime)
{
	// Gor each object
	for (uint i = 0; i < _ListInstance.size(); i++)
	{
		// Some animation in the list ?
		if (_ListInstance[i]->Saved.PlayList.size() > 0)
		{
			// Accumul time
			float startTime = 0;
			float endTime = 0;

			// Get start time of the animation that starts before the current time
			for (uint index = 0; index < _ListInstance[i]->Saved.PlayList.size(); index++)
			{
				// Pointer on the animation
				string& name = _ListInstance[i]->Saved.PlayList[index];
				CAnimation *anim = _ListInstance[i]->AnimationSet.getAnimation (_ListInstance[i]->AnimationSet.getAnimationIdByName(name));

				// Add start time
				startTime = endTime;
				endTime = startTime + anim->getEndTime()-anim->getBeginTime();

				if ((startTime <= currentTime) && (currentTime < endTime))
				{
					// setup the sound context

					DWORD tab[] = {IDC_ARG0, IDC_ARG1, IDC_ARG2, IDC_ARG3, };
					
					for (uint i = 0; i < 4; i++)
					{
						CEdit *edit = (CEdit*) _SoundAnimDlg->GetDlgItem(tab[i]);
						nlassert(edit);
						char str[1024];
						edit->GetLine(0, str, 1024);
						SoundContext.Args[i] = atoi (str);
					}

					// get the position of the skel if a skel is available
					if (SkeletonUsedForSound != 0xFFFFFFFF)
					{
						const CMatrix &m = _ListInstance[SkeletonUsedForSound]->TransformShape->getMatrix();
						SoundContext.Position = m.getPos();
					}
					else
					{
						SoundContext.Position = CVector::Null;
					}

					CSoundSystem::playAnimation(name, lastTime - startTime, currentTime - startTime, SoundContext);
				}
			}
		}
	}
}

// ***************************************************************************

/*
void CObjectViewer::evalSoundTrack (float lastTime, float currentTime)
{
	if (lastTime!=currentTime)
	{
		// For each objects
		for (uint instance=0; instance<_ListInstance.size(); instance++)
		{
			// For each channel of the mixer
			for (uint slot=0; slot<CChannelMixer::NumAnimationSlot; slot++)
			{
				// Anim id
				uint animId=_ListInstance[instance]->Playlist.getAnimation (slot);

				// Channel actif ?
				if (animId!=CAnimationPlaylist::empty)
				{
					// Get the animation
					CAnimation *anim=_ListInstance[instance]->AnimationSet.getAnimation (animId);
					nlassert (anim);

					// Get the sound track
					uint trackId=anim->getIdTrackByName ("NoteTrack");
					if (trackId!=CAnimation::NotFound)
					{
						// Get the track
						ITrack *track=anim->getTrack (trackId);
						nlassert (track);

						// Dynamic cast
						UTrackKeyframer *soundTrackKF = dynamic_cast<UTrackKeyframer *>(track);
						if (soundTrackKF)
						{
							// Sound keys
							std::vector<TAnimationTime> result;

							// Get local begin and endTime
							TAnimationTime localLastTime = _ListInstance[instance]->Playlist.getLocalTime (slot, lastTime, _ListInstance[instance]->AnimationSet);
							TAnimationTime localCurrentTime = _ListInstance[instance]->Playlist.getLocalTime (slot, currentTime, _ListInstance[instance]->AnimationSet);

							// Good interval
							if (localLastTime<=localCurrentTime)
							{
								// Get keys in this interval
								soundTrackKF->getKeysInRange(localLastTime, localCurrentTime, result);
							}
							else
							{
								// Get begin and last time
								TAnimationTime beginTime=track->getBeginTime ();
								TAnimationTime endTime=track->getEndTime ();

								// Time must have been clamped
								nlassert (localCurrentTime<=endTime);
								nlassert (localLastTime>=beginTime);

								// Get keys to the end
								soundTrackKF->getKeysInRange(localCurrentTime, endTime, result);

								// Get keys at the beginning
								soundTrackKF->getKeysInRange(beginTime, localLastTime, result);
							}

							// Process sounds
							NLSOUND::UAudioMixer *audioMixer = CSoundSystem::getAudioMixer ();
							if( audioMixer )
							{	
								vector<TAnimationTime>::iterator itResult;
								for( itResult = result.begin(); itResult != result.end(); ++itResult ) 
								{
									string soundName;
									double keyTime = *itResult;
									nlinfo("keyTime = %f  result size : %d",*itResult,result.size());
									
									if( !track->interpolate( *itResult, soundName) )
									{
										nlwarning("The key at offset %f is not a string",*itResult);
									}
									else
									{
										// if there are step sounds
										if( soundName == "step" )
										{
 											// need to spawn a sound linked to the anim
											string dummySound = "PAShommecourseappartdur1a";
											USource *source = audioMixer->createSource (dummySound.c_str() , true );
											if (source)
											{
												source->setPos (CVector::Null);
												source->play ();
 												nlinfo ("launching dummy sound %s for the step event", dummySound.c_str());
											}
											else
											{
	 											nlwarning ("sound not found for the step event: '%s'", dummySound.c_str());
											}
										}
 										else if (soundName.find ("snd_") != string::npos)
 										{
 											// need to spawn a sound linked to the anim
											USource *source = audioMixer->createSource ( soundName.c_str(), true );
											if (source)
											{
												source->setPos (CVector::Null);
												source->play ();
 												nlinfo ("launching sound for anim event from notetrack '%s'", soundName.c_str());
											}
											else
											{
	 											nlwarning ("sound not found: '%s'", soundName.c_str());
											}
 										}
 										else
 										{
 											nlwarning ("unknown notetrack event: '%s'", soundName.c_str());
 										}
									}
								}
							}
						}
					}
				}
			}
		}
	}
}
*/


// ***************************************************************************
uint CObjectViewer::addInstanceGroup(NL3D::CInstanceGroup *ig)
{
	// First instance
	uint first = (uint)_ListInstance.size();

	// Add all models to the scene		
	ig->addToScene(*CNELU::Scene, CNELU::Driver);
	// Unfreeze all objects from HRC.
	ig->unfreezeHRC();

	// link the root of the IG to our root, for scene rotation
	ig->linkRoot(*CNELU::Scene, _SceneRoot);

	// Keep a reference on them, but they'll be destroyed by IG.
	for (uint k = 0; k < ig->getNumInstance(); ++k)
	{
		CInstanceInfo *iInfo = new CInstanceInfo;
		iInfo->TransformShape = ig->_Instances[k];
		iInfo->Saved.ShapeFilename = ig->_InstancesInfos[k].Name;
		iInfo->MustDelete = false;
		_ListInstance.push_back (iInfo);
	}

	// Add the ig to the list.
	_ListIG.push_back(ig);

	// Return first instance
	return first;
}

// ***************************************************************************
void CObjectViewer::setupSceneLightingSystem(bool enable, const NLMISC::CVector &sunDir, NLMISC::CRGBA sunAmbiant, NLMISC::CRGBA sunDiffuse, NLMISC::CRGBA sunSpecular)
{
	CNELU::Scene->enableLightingSystem(enable);

	// Setup sun.
	CNELU::Scene->setSunAmbient(sunAmbiant);
	CNELU::Scene->setSunDiffuse(sunDiffuse);
	CNELU::Scene->setSunSpecular(sunSpecular);
	CNELU::Scene->setSunDirection(sunDir);
}


// ***************************************************************************
void CObjectViewer::enableDynamicObjectLightingTest(NLPACS::CGlobalRetriever *globalRetriever, NL3D::CInstanceGroup *ig)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	// first delete the instance
	if(_ObjectLightTest)
	{
		CNELU::Scene->deleteInstance(_ObjectLightTest);
		_ObjectLightTest= NULL;
	}

	_GlobalRetriever= globalRetriever;

	// if enabled
	if(_GlobalRetriever)
	{
		nlassert(ig);

		// this mesh is the dynamic one to move around.
		_ObjectLightTest= CNELU::Scene->createInstance(_ObjectLightTestShape);
		if(_ObjectLightTest!=NULL)
		{
			// link to the root for manipulation
			_SceneRoot->hrcLinkSon(_ObjectLightTest);

			// setup the matrix.
			_ObjectLightTestMatrix= _ObjectLightTest->getMatrix();
			// setup the logic info.
			_ObjectLightTestLogicInfo.GlobalRetriever= _GlobalRetriever;
			_ObjectLightTestLogicInfo.Ig= ig;
			// Default the GPos to uninitialized
			_ObjectLightTestLogicInfo.GPos.InstanceId= -1;
			_ObjectLightTest->setLogicInfo(&_ObjectLightTestLogicInfo);
		}
		else
		{
			if (!_ObjectLightTestShape.empty())
			{			
				string	str= string("Path not found for Light Test Shape: ") + _ObjectLightTestShape;
				::MessageBox(NULL, str.c_str(), "Dynamic Object Light Test", MB_OK|MB_ICONEXCLAMATION);
			}
			// disable.
			_ObjectLightTest= NULL;
			_GlobalRetriever= NULL;
		}
	}
}


// ***************************************************************************
void	CObjectViewer::COVLogicInfo::getStaticLightSetup(NLMISC::CRGBA sunAmbient, std::vector<CPointLightInfluence> &pointLightList, uint8 &sunContribution, CRGBA &ambient)
{
	Ig->getStaticLightSetup(sunAmbient, GlobalRetriever->getLocalRetrieverId(GPos), 
		GPos.LocalPosition.Surface,
		GPos.LocalPosition.Estimation, 
		pointLightList, sunContribution, ambient);
}


// ***************************************************************************
// ***************************************************************************
// Vegetable Landscape Part.
// ***************************************************************************
// ***************************************************************************


// ***************************************************************************
void		CObjectViewer::loadVegetableLandscapeCfg(NLMISC::CConfigFile &cf)
{
	// vegetable display is true by default.
	_VegetableEnabled= true;
	_VegetableSnapToGround= true;


	// Load landscape setup
	// --------------
	try
	{
		// tileBank setup.
		CConfigFile::CVar &tileBank = cf.getVar("veget_tile_bank");
		_VegetableLandscapeTileBank= tileBank.asString();
		CConfigFile::CVar &tileFarBank = cf.getVar("veget_tile_far_bank");
		_VegetableLandscapeTileFarBank= tileFarBank.asString();
		// zone list.
		_VegetableLandscapeZoneNames.clear();
		CConfigFile::CVar &zones = cf.getVar("veget_landscape_zones");
		for (uint i=0; i<(uint)zones.size(); i++)
			_VegetableLandscapeZoneNames.push_back(zones.asString(i).c_str());
	}
	catch (EUnknownVar &)
	{
		_VegetableLandscapeTileBank.clear();
		_VegetableLandscapeTileFarBank.clear();
		_VegetableLandscapeZoneNames.clear();
	}


	// Load Landscape params.
	// --------------
	// threshold
	try
	{
		CConfigFile::CVar &thre= cf.getVar("veget_landscape_threshold");
		_VegetableLandscapeThreshold= thre.asFloat();
		// clamp to avoid divide/0.
		_VegetableLandscapeThreshold= max(_VegetableLandscapeThreshold, 0.001f);
	}
	catch (EUnknownVar &)
	{
		_VegetableLandscapeThreshold= 0.003f;
	}
	// tilenear
	try
	{
		CConfigFile::CVar &tileNear= cf.getVar("veget_landscape_tile_near");
		_VegetableLandscapeTileNear= tileNear.asFloat();
	}
	catch (EUnknownVar &)
	{
		_VegetableLandscapeTileNear= 50;
	}
	// ambient
	try
	{
		CConfigFile::CVar &color= cf.getVar("veget_landscape_ambient");
		_VegetableLandscapeAmbient.R= color.asInt(0);
		_VegetableLandscapeAmbient.G= color.asInt(1);
		_VegetableLandscapeAmbient.B= color.asInt(2);
	}
	catch (EUnknownVar &)
	{
		_VegetableLandscapeAmbient.set(80, 80, 80);
	}
	// diffuse
	try
	{
		CConfigFile::CVar &color= cf.getVar("veget_landscape_diffuse");
		_VegetableLandscapeDiffuse.R= color.asInt(0);
		_VegetableLandscapeDiffuse.G= color.asInt(1);
		_VegetableLandscapeDiffuse.B= color.asInt(2);
	}
	catch (EUnknownVar &)
	{
		_VegetableLandscapeDiffuse.set(255, 255, 255);
	}
	// Snapping
	try
	{
		CConfigFile::CVar &var= cf.getVar("veget_landscape_snap_height");
		_VegetableSnapHeight= var.asFloat();
	}
	catch (EUnknownVar &)
	{
		_VegetableSnapHeight= 1.70f;
	}


	// Load Vegetable params.
	// --------------

	// vegetable texture
	try
	{
		CConfigFile::CVar &var= cf.getVar("veget_texture");
		_VegetableTexture= var.asString();
	}
	catch (EUnknownVar &)
	{
		_VegetableTexture= "";
	}

	// vegetable ambient
	try
	{
		CConfigFile::CVar &color= cf.getVar("veget_ambient");
		_VegetableAmbient.R= color.asInt(0);
		_VegetableAmbient.G= color.asInt(1);
		_VegetableAmbient.B= color.asInt(2);
	}
	catch (EUnknownVar &)
	{
		_VegetableAmbient.set(80, 80, 80);
	}
	// vegetable diffuse
	try
	{
		CConfigFile::CVar &color= cf.getVar("veget_diffuse");
		// setup to behave correclty ie as maxLightFactor:
		sint	R= color.asInt(0) - _VegetableAmbient.R;	clamp(R, 0, 255);	_VegetableDiffuse.R= R;
		sint	G= color.asInt(1) - _VegetableAmbient.G;	clamp(G, 0, 255);	_VegetableDiffuse.G= G;
		sint	B= color.asInt(2) - _VegetableAmbient.B;	clamp(B, 0, 255);	_VegetableDiffuse.B= B;
	}
	catch (EUnknownVar &)
	{
		sint	R= 255 - _VegetableAmbient.R;	clamp(R, 0, 255);	_VegetableDiffuse.R= R;
		sint	G= 255 - _VegetableAmbient.G;	clamp(G, 0, 255);	_VegetableDiffuse.G= G;
		sint	B= 255 - _VegetableAmbient.B;	clamp(B, 0, 255);	_VegetableDiffuse.B= B;
	}
	// vegetable lightDir
	try
	{
		CConfigFile::CVar &var= cf.getVar("veget_light_dir");
		_VegetableLightDir.x= var.asFloat(0);
		_VegetableLightDir.y= var.asFloat(1);
		_VegetableLightDir.z= var.asFloat(2);
		_VegetableLightDir.normalize();
	}
	catch (EUnknownVar &)
	{
		_VegetableLightDir.set(0, 1, -1);
		_VegetableLightDir.normalize();
	}

	// windDir
	try
	{
		CConfigFile::CVar &var= cf.getVar("veget_wind_dir");
		_VegetableWindDir.x= var.asFloat(0);
		_VegetableWindDir.y= var.asFloat(1);
		_VegetableWindDir.z= var.asFloat(2);
	}
	catch (EUnknownVar &)
	{
		_VegetableWindDir.x= 0.5f;
		_VegetableWindDir.y= 0.5f;
		_VegetableWindDir.z= 0;
	}
	// windFreq
	try
	{
		CConfigFile::CVar &var= cf.getVar("veget_wind_freq");
		_VegetableWindFreq= var.asFloat();
	}
	catch (EUnknownVar &)
	{
		_VegetableWindFreq= 0.5;
	}
	// windPower
	try
	{
		CConfigFile::CVar &var= cf.getVar("veget_wind_power");
		_VegetableWindPower= var.asFloat();
	}
	catch (EUnknownVar &)
	{
		_VegetableWindPower= 1;
	}
	// windBendMin
	try
	{
		CConfigFile::CVar &var= cf.getVar("veget_wind_bend_min");
		_VegetableWindBendMin= var.asFloat();
	}
	catch (EUnknownVar &)
	{
		_VegetableWindBendMin= 0;
	}


}


// ***************************************************************************
bool		CObjectViewer::createVegetableLandscape()
{
	// If not already done.
	if(!_VegetableLandscape)
	{
		// create the landscape.
		_VegetableLandscape= static_cast<CLandscapeModel*>(CNELU::Scene->createModel(LandscapeModelId));

		// Create a Progress Dialog.
		CDialogProgress		dlgProgress;
		dlgProgress.Create(CDialogProgress::IDD, _MainFrame);
		dlgProgress.ShowWindow(true);

		try
		{
			if(_VegetableLandscapeTileBank=="")
			{
				throw Exception("Landscape CFG not fully defined");
			}

			// Load The Bank files (copied from CLandscapeUser :) ).
			// ================
			// progress
			dlgProgress.ProgressText.SetWindowText("Loading TileBanks...");
			dlgProgress.ProgressBar.SetPos(0);
			// load
			CIFile bankFile(CPath::lookup(_VegetableLandscapeTileBank));
			_VegetableLandscape->Landscape.TileBank.serial(bankFile);
			_VegetableLandscape->Landscape.TileBank.makeAllPathRelative();
			_VegetableLandscape->Landscape.TileBank.makeAllExtensionDDS();
			_VegetableLandscape->Landscape.TileBank.setAbsPath ("");

			// progress
			dlgProgress.ProgressBar.SetPos(50);
			// load
			CIFile farbankFile(CPath::lookup(_VegetableLandscapeTileFarBank));
			_VegetableLandscape->Landscape.TileFarBank.serial(farbankFile);
			if ( ! _VegetableLandscape->Landscape.initTileBanks() )
			{
				nlwarning( "You need to recompute bank.farbank for the far textures" );
			}
			bankFile.close();
			farbankFile.close();


			// flushTiles.
			// ================
			if(CNELU::Driver)
			{
				// progress
				dlgProgress.ProgressText.SetWindowText("Loading Tiles...");
				dlgProgress.ProgressBar.SetPos(0);

				// count nbText to load.
				sint	ts;
				sint	nbTextTotal= 0;
				for (ts=0; ts<_VegetableLandscape->Landscape.TileBank.getTileSetCount (); ts++)
				{
					CTileSet *tileSet=_VegetableLandscape->Landscape.TileBank.getTileSet (ts);
					nbTextTotal+= tileSet->getNumTile128();
					nbTextTotal+= tileSet->getNumTile256();
					nbTextTotal+= CTileSet::count;
				}

				// load.
				sint	nbTextDone= 0;
				for (ts=0; ts<_VegetableLandscape->Landscape.TileBank.getTileSetCount (); ts++)
				{
					CTileSet *tileSet=_VegetableLandscape->Landscape.TileBank.getTileSet (ts);
					sint tl;
					for (tl=0; tl<tileSet->getNumTile128(); tl++, nbTextDone++)
					{
						_VegetableLandscape->Landscape.flushTiles (CNELU::Driver, (uint16)tileSet->getTile128(tl), 1);
						dlgProgress.ProgressBar.SetPos(nbTextDone*100/nbTextTotal);
					}
					for (tl=0; tl<tileSet->getNumTile256(); tl++, nbTextDone++)
					{
						_VegetableLandscape->Landscape.flushTiles (CNELU::Driver, (uint16)tileSet->getTile256(tl), 1);
						dlgProgress.ProgressBar.SetPos(nbTextDone*100/nbTextTotal);
					}
					for (tl=0; tl<CTileSet::count; tl++, nbTextDone++)
					{
						_VegetableLandscape->Landscape.flushTiles (CNELU::Driver, (uint16)tileSet->getTransition(tl)->getTile (), 1);
						dlgProgress.ProgressBar.SetPos(nbTextDone*100/nbTextTotal);
					}
				}
			}


			// misc setup.
			// ================
			_VegetableLandscape->Landscape.setThreshold(_VegetableLandscapeThreshold);
			_VegetableLandscape->Landscape.setTileNear(_VegetableLandscapeTileNear);
			_VegetableLandscape->Landscape.setupStaticLight(_VegetableLandscapeDiffuse, _VegetableLandscapeAmbient, 1);
			_VegetableLandscape->Landscape.loadVegetableTexture(_VegetableTexture);
			_VegetableLandscape->Landscape.setupVegetableLighting(_VegetableAmbient, _VegetableDiffuse, _VegetableLightDir);
			_VegetableLandscape->Landscape.setVegetableWind(_VegetableWindDir, _VegetableWindFreq, _VegetableWindPower, _VegetableWindBendMin);


			// Load the zones.
			// ================
			// landscape recentering.
			bool	zoneLoaded= false;
			CAABBox	landscapeBBox;
			// progress
			dlgProgress.ProgressText.SetWindowText("Loading Zones...");
			dlgProgress.ProgressBar.SetPos(0);
			uint	nbZones= (uint)_VegetableLandscapeZoneNames.size();
			for(uint i=0; i<nbZones;i++)
			{
				// open the file
				CIFile	zoneFile(CPath::lookup(_VegetableLandscapeZoneNames[i]));
				CZone	zone;
				// load
				zoneFile.serial(zone);
				// append to landscape
				_VegetableLandscape->Landscape.addZone(zone);
				// progress
				dlgProgress.ProgressBar.SetPos(i*100/nbZones);

				// Add to the bbox.
				if(!zoneLoaded)
				{
					zoneLoaded= true;
					landscapeBBox.setCenter(zone.getZoneBB().getCenter());
				}
				else
					landscapeBBox.extend(zone.getZoneBB().getCenter());
			}

			// After All zone loaded, recenter the mouse listener on the landscape.
			if(zoneLoaded)
			{
				CMatrix		matrix;
				_MouseListener.setHotSpot(landscapeBBox.getCenter());
				matrix.setPos(landscapeBBox.getCenter());
				matrix.rotateX(-(float)Pi/4);
				matrix.translate(CVector(0,-1000,0));
				_MouseListener.setMatrix(matrix);
			}

			// Create collisions objects.
			_VegetableCollisionManager= new CVisualCollisionManager;
			_VegetableCollisionManager->setLandscape(&_VegetableLandscape->Landscape);
			_VegetableCollisionEntity= _VegetableCollisionManager->createEntity();
		}
		catch (Exception &e)
		{
			// close the progress dialog
			dlgProgress.DestroyWindow();

			MessageBox(_MainFrame->m_hWnd, e.what(), "Failed to Load landscape", MB_OK | MB_APPLMODAL);

			// remove first possibly created collisions objects.
			if(_VegetableCollisionEntity)
			{
				_VegetableCollisionManager->deleteEntity(_VegetableCollisionEntity);
				_VegetableCollisionEntity= NULL;
			}
			if(_VegetableCollisionManager)
			{
				delete _VegetableCollisionManager;
				_VegetableCollisionManager= NULL;
			}

			// remove the landscape
			CNELU::Scene->deleteModel(_VegetableLandscape);
			_VegetableLandscape= NULL;

			return false;
		}

		// close the progress dialog
		dlgProgress.DestroyWindow();
	}

	return true;
}


// ***************************************************************************
void		CObjectViewer::showVegetableLandscape()
{
	if(_VegetableLandscape)
	{
		_VegetableLandscape->show();
	}
}

// ***************************************************************************
void		CObjectViewer::hideVegetableLandscape()
{
	if(_VegetableLandscape)
	{
		_VegetableLandscape->hide();
	}
}


// ***************************************************************************
void		CObjectViewer::enableLandscapeVegetable(bool enable)
{
	// update
	_VegetableEnabled= enable;

	// update view.
	if(_VegetableLandscape)
	{
		_VegetableLandscape->Landscape.enableVegetable(_VegetableEnabled);
	}
}


// ***************************************************************************
void		CObjectViewer::refreshVegetableLandscape(const NL3D::CTileVegetableDesc &tvdesc)
{
	// if landscape is displayed.
	if(_VegetableLandscape)
	{
		// first disable the vegetable, to delete any vegetation
		_VegetableLandscape->Landscape.enableVegetable(false);

		// Then change all the tileSet of all the TileBanks.
		for (sint ts=0; ts<_VegetableLandscape->Landscape.TileBank.getTileSetCount (); ts++)
		{
			CTileSet *tileSet=_VegetableLandscape->Landscape.TileBank.getTileSet (ts);
			// change the vegetableTileDesc of this tileSet.
			tileSet->setTileVegetableDesc(tvdesc);
		}

		// re-Enable the vegetable (if wanted).
		_VegetableLandscape->Landscape.enableVegetable(_VegetableEnabled);
	}
}


// ***************************************************************************
void		CObjectViewer::setVegetableWindPower(float w)
{
	_VegetableWindPower= w;
	if(_VegetableLandscape)
		_VegetableLandscape->Landscape.setVegetableWind(_VegetableWindDir, _VegetableWindFreq, _VegetableWindPower, _VegetableWindBendMin);
}
// ***************************************************************************
void		CObjectViewer::setVegetableWindBendStart(float w)
{
	_VegetableWindBendMin= w;
	if(_VegetableLandscape)
		_VegetableLandscape->Landscape.setVegetableWind(_VegetableWindDir, _VegetableWindFreq, _VegetableWindPower, _VegetableWindBendMin);
}
// ***************************************************************************
void		CObjectViewer::setVegetableWindFrequency(float w)
{
	_VegetableWindFreq= w;
	if(_VegetableLandscape)
		_VegetableLandscape->Landscape.setVegetableWind(_VegetableWindDir, _VegetableWindFreq, _VegetableWindPower, _VegetableWindBendMin);
}


// ***************************************************************************
void		CObjectViewer::snapToGroundVegetableLandscape(bool enable)
{
	// update
	_VegetableSnapToGround= enable;
}

// ***************************************************************************
CInstanceInfo::CInstanceInfo ()
{
	TransformShape = NULL;
	Camera = NULL;
	MustDelete = false;
}

// ***************************************************************************
CInstanceInfo::~CInstanceInfo ()
{
	if (MustDelete)
	{
		if (TransformShape)
			CNELU::Scene->deleteInstance (TransformShape);
		if (Camera)
			CNELU::Scene->deleteModel (Camera);
	}
}

// ***************************************************************************
CSlotInfo::CSlotInfo ()
{
	StartTime = 0;
	EndTime = 0;
	Offset = 0;
	StartBlend = 1;
	EndBlend = 1;
	Smoothness = 1;
	SpeedFactor = 1;
	ClampMode = 0;
	SkeletonInverted = false;
	Enable = true;
}

// ***************************************************************************
void CSlotInfo::serial (NLMISC::IStream &f)
{
	f.serialVersion (0);
	f.serial (Animation);
	f.serial (Skeleton);
	f.serial (Offset);
	f.serial (StartTime);
	f.serial (EndTime);
	f.serial (StartBlend);
	f.serial (EndBlend);
	f.serial (Smoothness);
	f.serial (SpeedFactor);
	f.serial (ClampMode);
	f.serial (SkeletonInverted);
	f.serial (Enable);
}

// ***************************************************************************
uint CObjectViewer::getEditedObject ()
{
	return _SelectedObject;
}

// ***************************************************************************
void CObjectViewer::setEditedObject (uint selected)
{
	_SelectedObject=selected;
}

// ***************************************************************************
CInstanceInfo *CObjectViewer::getInstance (uint instance)
{
	return _ListInstance[instance];
}

// ***************************************************************************
uint CObjectViewer::getNumInstance () const
{
	return (uint)_ListInstance.size ();
}

// ***************************************************************************
void CInstanceInfo::setAnimationPlaylist (float frameRate)
{
	for (uint id=0; id<NL3D::CChannelMixer::NumAnimationSlot; id++)
	{
		if (Saved.SlotInfo[id].Enable)
		{
			// Set the animation
			uint animId = AnimationSet.getAnimationIdByName (Saved.SlotInfo[id].Animation);
			if (animId == CAnimationSet::NotFound)
				Playlist.setAnimation (id, CAnimationPlaylist::empty);
			else			
				Playlist.setAnimation (id, animId);

			// Set the skeleton weight
			uint skelId = AnimationSet.getSkeletonWeightIdByName (Saved.SlotInfo[id].Skeleton);
			if (skelId == CAnimationSet::NotFound)
				Playlist.setSkeletonWeight (id, CAnimationPlaylist::empty, false);
			else
				Playlist.setSkeletonWeight (id, skelId, Saved.SlotInfo[id].SkeletonInverted);

			// Set others values
			Playlist.setTimeOrigin (id, Saved.SlotInfo[id].Offset/frameRate);
			Playlist.setSpeedFactor (id, Saved.SlotInfo[id].SpeedFactor);
			Playlist.setStartWeight (id, Saved.SlotInfo[id].StartBlend, Saved.SlotInfo[id].StartTime/frameRate);
			Playlist.setEndWeight (id, Saved.SlotInfo[id].EndBlend, Saved.SlotInfo[id].EndTime/frameRate);
			Playlist.setWeightSmoothness (id, Saved.SlotInfo[id].Smoothness);

			// Switch between wrap modes
			switch (Saved.SlotInfo[id].ClampMode)
			{
			case 0:
				Playlist.setWrapMode (id, CAnimationPlaylist::Clamp);
				break;
			case 1:
				Playlist.setWrapMode (id, CAnimationPlaylist::Repeat);
				break;
			case 2:
				Playlist.setWrapMode (id, CAnimationPlaylist::Disable);
				break;
			}
		}
	}
}

// ***************************************************************************
CInstanceSave::CInstanceSave ()
{
	SkeletonId = 0xffffffff;
	IsSkeleton = false;
	Camera = false;
}

// ***************************************************************************
void CInstanceSave::serial (NLMISC::IStream &f)
{
	// Serial a version
	sint ver = f.serialVersion (1);

	// Play list of this object
	f.serialCont (PlayList);

	// Slot info for this object
	nlassert (NL3D::CChannelMixer::NumAnimationSlot == 8);
	for (uint slot=0; slot<8; slot++)
		// Serial the slot information
		f.serial (SlotInfo[slot]);

	// Input file
	f.serial (ShapeFilename);

	// Skeleton id
	f.serial (SkeletonId);

	// Bind bone name
	f.serial (BindBoneName);

	// Is a skeleton
	f.serial (IsSkeleton);

	// Animation input file
	f.serialCont (AnimationFileName);

	// Skeleton weight input file
	f.serialCont (SWTFileName);

	// Is a camera
	if (ver>=1)
	{
		f.serial (Camera);
		f.serial (CameraInfo);
	}
	else if (f.isReading ())
		Camera = false;
}

// ***************************************************************************
void CObjectViewer::refreshAnimationListeners()
{
	_SoundAnimDlg->refresh (TRUE);
}

// ***************************************************************************
void CObjectViewer::addAnimation (NL3D::CAnimation* anim, const char* filename, const char* name, uint instance)
{
	// Add an animation
	uint id = _ListInstance[instance]->AnimationSet.addAnimation (name, anim);
	_ListInstance[instance]->Saved.AnimationFileName.push_back (filename);

	// Rebuild the animationSet
	_ListInstance[instance]->AnimationSet.build ();
	if(CNELU::Driver && CNELU::ShapeBank)
	{
		_ListInstance[instance]->AnimationSet.preloadSSSShapes(*CNELU::Driver, *CNELU::ShapeBank);
	}

	_SoundAnimDlg->refresh (TRUE);
}

// ***************************************************************************
void CObjectViewer::loadAnimation (const char* fileName, uint instance)
{
	// Open the file
	CIFile file;
	if (file.open (fileName))
	{
		// Get the animation name
		char name[256];
		_splitpath (fileName, NULL, NULL, name, NULL);

		// Make an animation
		CAnimation *anim=new CAnimation;

		// Serial it
		anim->serial (file);

		// Add the animation
		addAnimation (anim, fileName, name, instance);
	}
	else
	{
		// Create a message
		char msg[512];
		_snprintf (msg, 512, "Can't open the file %s for reading.", fileName);
		_MainFrame->MessageBox (msg, "NeL object viewer", MB_OK|MB_ICONEXCLAMATION);
	}
}

// ***************************************************************************
void CObjectViewer::loadSWT (const char* fileName, uint instance)
{
	// Open the file
	CIFile file;
	if (file.open (fileName))
	{
		// Get the animation name
		char name[256];
		_splitpath (fileName, NULL, NULL, name, NULL);

		// Get the skeleton pointer
		CSkeletonWeight* skel=new CSkeletonWeight;

		// Serial it
		skel->serial (file);

		// Add an animation
		_ListInstance[instance]->AnimationSet.addSkeletonWeight (name, skel);

		// Add the filename in the list
		_ListInstance[instance]->Saved.SWTFileName.push_back (fileName);
	}
	else
	{
		// Create a message
		char msg[512];
		_snprintf (msg, 512, "Can't open the file %s for reading.", fileName);
		_MainFrame->MessageBox (msg, "NeL object viewer", MB_OK|MB_ICONEXCLAMATION);
	}
}

// ***************************************************************************
CMainDlg *CObjectViewer::getSlotDlg ()
{
	return _SlotDlg;
}

// ***************************************************************************
void CObjectViewer::reloadTextures ()
{
	// For each instances
	uint numInstance = getNumInstance ();
	uint instance;
	for (instance=0; instance<numInstance; instance++)
	{
		// Get the info
		CInstanceInfo *info = getInstance (instance);

		// For each material
		if (info->TransformShape)
		{
			uint numMaterial = info->TransformShape->getNumMaterial ();
			uint mat;
			for (mat=0; mat<numMaterial; mat++)
			{
				// Get the material
				CMaterial *material = info->TransformShape->getMaterial (mat);

				// For each texture
				int tex;
				for (tex=0; tex<IDRV_MAT_MAXTEXTURES; tex++)
				{
					ITexture *texture = material->getTexture (tex);

					// Touch it!
					if (texture)
					{
						CNELU::Driver->invalidateShareTexture (*texture);
					}
				}
			}
		}
	}
}

// ***************************************************************************
// ***************************************************************************
// Global wind part.
// ***************************************************************************
// ***************************************************************************


// ***************************************************************************
void		CObjectViewer::setGlobalWindPower(float w)
{
	if(_MainFrame)
	{
		clamp(w, 0.f, 1.f);
		_MainFrame->GlobalWindPower= w;
		CNELU::Scene->setGlobalWindPower(w);
	}
}


// ***************************************************************************
float		CObjectViewer::getGlobalWindPower() const
{
	if(_MainFrame)
		return _MainFrame->GlobalWindPower;
	else
		return 1.f;
}


void		CObjectViewer::shootScene() 
{
	static const char BASED_CODE szFilter[] = "Targa Files (*.tga)|*.tga|Jpeg Files (*.jpg)|*.jpg|All Files (*.*)|*.*||";
	CFileDialog fileDlg ( FALSE, ".tga", "*.tga", OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT, szFilter);
	if (fileDlg.DoModal () == IDOK)
	{
		// Choose the size
		CSelectMovieSize movieSize;
		if (movieSize.DoModal () == IDOK)
		{
			// Resize the window
			RECT window;
			_MainFrame->GetWindowRect (&window);
			uint32 width;
			uint32 height;
			CNELU::Driver->getWindowSize (width, height);
			window.right += movieSize.Width - width;
			window.bottom += movieSize.Height - height;
			_MainFrame->SetWindowPos (NULL, 0, 0, window.right-window.left, window.bottom-window.top, SWP_NOMOVE|SWP_NOZORDER);
			

			// Swap the buffers
			CNELU::swapBuffers();
			CNELU::Driver->setupViewport (CViewport ());

			// The file name
			string filename = NLMISC::CFile::getFilenameWithoutExtension ((const char*)fileDlg.GetPathName ());
			string extension = NLMISC::CFile::getExtension ((const char*)fileDlg.GetPathName ());

			// The file name without extension
			bool jpeg = toLower (extension) == "jpg";

			// Activate the driver
 			CNELU::Driver->activate ();

			// Bitmap for shoot
			NLMISC::CBitmap shoot;

			// For each frame
			sint i;
			for (i=(sint)_AnimationDlg->Start; i<=(sint)_AnimationDlg->End; i++)
			{
				// Set the time
				_AnimationDlg->setCurrentFrame ((float)i);

				// Setup the play list
				setupPlaylist (_AnimationDlg->getTime());

				// Animate the automatic animation in the scene
				animateCNELUScene (_CS, (uint64)(1000.f / _AnimationDlg->Speed));

				// Eval channel mixer for transform
				for (uint j=0; j<_ListInstance.size(); j++)
					_ListInstance[j]->ChannelMixer.eval (false);

				// Clear the buffers
				CNELU::clearBuffers (_BackGroundColor);

				// call of callback list
				{
					std::vector<IMainLoopCallBack *> copyVect(_CallBackList.begin(), _CallBackList.end());
					for (std::vector<IMainLoopCallBack *>::iterator it = _CallBackList.begin(); it != _CallBackList.end(); ++it)
					{
						(*it)->goPreRender();
					}
				}
				// Render the CS
				if (_CS) _CS->render ();			
			
				// Draw the scene		
				CNELU::Scene->render();	
				
				// call of callback list
				{
					std::vector<IMainLoopCallBack *> copyVect(_CallBackList.begin(), _CallBackList.end());
					for (std::vector<IMainLoopCallBack *>::iterator it = _CallBackList.begin(); it != _CallBackList.end(); ++it)
					{
						(*it)->goPostRender();
					}
				}
				


				// Swap the buffers
				CNELU::swapBuffers();

				// Get the buffer
				CNELU::Driver->getBuffer (shoot);
				shoot.flipV ();

				// Save it
				char num[12];
				smprintf (num, 12, "%04d", i);
				string filenamefinal = filename+num+string (".")+extension;
				try
				{
					NLMISC::COFile output;
					if (output.open (filenamefinal))
					{
						if (jpeg)
							shoot.writeJPG ( output, 255 );
						else
							shoot.writeTGA ( output, 32 );
					}
					else
					{
						_MainFrame->MessageBox (("Can't open the file "+filenamefinal+" for writing.").c_str (), "NeL object viewer", MB_OK|MB_ICONEXCLAMATION);
						break;
					}
				}
				catch (Exception &e)
				{
					_MainFrame->MessageBox (("Error during writing of the file "+filenamefinal+" : "+(string)e.what ()).c_str (), "NeL object viewer", MB_OK|MB_ICONEXCLAMATION);
					break;
				}
			}
		}
	}
}

void CObjectViewer::drawFXUserMatrix()

{
	static std::string fxUserMatrixStr;
	static bool stringRetrieved = false;
	if (!stringRetrieved)
	{	
		CString fxUserMatrix;
		fxUserMatrix.LoadString(IDS_FX_USER_MATRIX);
		fxUserMatrixStr = (LPCTSTR) fxUserMatrix;
		stringRetrieved = true;
	}
	nlassert(_ParticleDlg);
	drawNamedMatrix(getFXUserMatrix(), fxUserMatrixStr, NLMISC::CRGBA::Red, 0.2f, 10.f);
}

void CObjectViewer::drawFXMatrix()
{
	static std::string fxStr;
	static bool stringRetrieved = false;
	if (!stringRetrieved)
	{	
		CString fx;
		fx.LoadString(IDS_FX_MATRIX);
		fxStr = (LPCTSTR) fx;
		stringRetrieved = true;
	}
	drawNamedMatrix(_ParticleDlg->getPSWorldMatrix(), fxStr, NLMISC::CRGBA::Blue, -0.2f, 10.f);
}

void CObjectViewer::drawSceneMatrix()
{
	static std::string sceneMatrixStr;
	static bool stringRetrieved = false;
	if (!stringRetrieved)
	{	
		CString sceneMatrix;
		sceneMatrix.LoadString(IDS_SCENE_MATRIX);
		sceneMatrixStr = (LPCTSTR) sceneMatrix;
		stringRetrieved = true;
	}
	drawNamedMatrix(_SceneRoot->getMatrix(), sceneMatrixStr, NLMISC::CRGBA::White, 0.f, 10.f);
}


void CObjectViewer::drawNamedMatrix(const NLMISC::CMatrix &matrix, const std::string &name, NLMISC::CRGBA color, float textZOffset, float testSize)
{
	CPSUtil::displayBasis(CNELU::Driver, matrix, NLMISC::CMatrix::Identity, 1.f, *_FontGenerator, _FontManager);	
	CPSUtil::print(CNELU::Driver, name, *_FontGenerator, _FontManager, matrix.getPos() + NLMISC::CVector(0.f, 0.f, textZOffset), testSize,  color);
}



sint CObjectViewer::getCurrentCamera () const
{
	return _CurrentCamera;
}
	
void CObjectViewer::setCurrentCamera (sint currentCamera)
{
	nlassert ((currentCamera == -1) ||(currentCamera < (sint)_Cameras.size ()));
	_CurrentCamera = currentCamera;
}
	
uint CObjectViewer::getCameraInstance (uint cameraId) const
{
	return _Cameras[cameraId];
}

uint CObjectViewer::getNumCamera () const
{
	return (uint)_Cameras.size ();
}

int localizedMessageBox(HWND parentWindow, int messageStringID, int captionStringID, UINT nType)
{
	
	CString caption;
	CString mess;
	caption.LoadString(captionStringID);
	mess.LoadString(messageStringID);
	return MessageBox(parentWindow, (LPCTSTR) mess, (LPCTSTR) caption, nType);
	// TODO : replace older call to ::MessageBox in the object viewer with that function
}

int localizedMessageBox(HWND parentWindow, const char *message, int captionStringID, UINT nType)
{
	CString caption;	
	caption.LoadString(captionStringID);	
	return MessageBox(parentWindow, message, (LPCTSTR) caption, nType);
}

CString getStrRsc(uint stringID)
{
	CString str;
	str.LoadString(stringID);
	return str;
}

bool browseFolder(const CString &caption, CString &destFolder, HWND parent)
{
	char chosenPath[MAX_PATH];
	// browse folder	
	BROWSEINFO bi;		
	bi.hwndOwner = parent;
	bi.pidlRoot = NULL;
	bi.pszDisplayName = chosenPath;
	bi.lpszTitle = (LPCTSTR) caption;
	bi.ulFlags = BIF_DONTGOBELOWDOMAIN | BIF_EDITBOX;
	bi.lpfn = NULL;
	bi.lParam = NULL;
	bi.iImage = 0;
	LPITEMIDLIST result = SHBrowseForFolder(&bi);
	if (result != NULL && SHGetPathFromIDList(result, chosenPath))
	{
		destFolder = chosenPath;
		return true;
	}
	return false;
}