HOWTO Guide: Unmanaged Irrlicht in .NET WinForms

A forum to store posts deemed exceptionally wise and useful
Cobra El Diablo
Posts: 3
Joined: Fri Sep 25, 2009 10:39 pm
Location: UK

Post by Cobra El Diablo »

Here are some code snippets that are being used with threading, .NET application and Irrlicht (1.5.1)

thread method which is created by the dll using the native WIN32 API call (CreateThread).

Code: Select all

void	threadFunc( LPVOID *lpvData )
{
	logMessage( "gseEngine threadFunc, start run ... \n" );
	while ( gseEngine ) {
		if ( !gseEngine->isPaused() && !gseEngine->isShuttingDown() )
			if ( gseEngine->getCamera() )
				gseEngine->Render();
	}
	logMessage( "gseEngine threadFunc, end run ... %d\n", nOpenGSE );
}
CLASS DEFINITION. Note the use of a singleton to make sure that one, and only one instance
of the engine is available at any given time.

Code: Select all

	//!	\class	COpenGSE
	//!	\brief	This class is exported from the OpenGSE.dll and provides the main
	//!			interface to the OpenGSE game/simulation engine. Note that it is
	//!			a multi-threaded dll and if using with a .NET application compile
	//!			the .NET application with /clr compiler switch.
	// : public OpenGSE::gseSingleton<OpenGSE::COpenGSE>
	class	OPENGSE_API	COpenGSE : public OpenGSE::gseSingleton<OpenGSE::COpenGSE>
	{
		protected:
		private:
			irr::IrrlichtDevice			*mDevice;			//	Device pointer to Irrlicht rendering device, D3D8/9 or OGL interface.
			irr::scene::ISceneManager	*sceneMgr;			//	Scene manager from Irrlicht.
			irr::gui::IGUIEnvironment	*envMgr;			//	GUI manager from Irrlicht.
			irr::gui::IGUIFont			*defaultFont;
			IrrPhysx::IPhysxManager		*physxMgr;			//	PhysX interface.
			IrrPhysx::SSceneDesc		sceneDesc;			//	PhysX scene description.
			IrrAI::IAIManager			*aiMgr;				//	AI interface.
			irr::scene::ISceneNode		*rootNode;			//	root scene node.
			gseDataSerializer			*dataSerializer;	//	data serializer input/output of custom scene data.
			gseEventReceiver			*eventReceiver;		//	event receiver.
			HWND						m_hWindow;			//	Window that Irrlicht is running in.
			WNDPROC						wndProc;			//	Old window message handler.
			irr::scene::ICameraSceneNode	*cameraNode;	// Current camera in use.
#if	defined( USE_INTEL_TERRAIN )
			gseTerrainManager			*terrainMgr;
#endif
#if	defined( USE_SHIFT_TERRAIN )
			gseTerrain					*terrainMgr;
#endif
#if	defined( USE_PAGED_TERRAIN )
			CTerrainPager	*terrainMgr;
#endif

			irr::core::map<irr::core::stringc, gseObjectArray>				objectArray;

			//	thread for gseEngine tasks.
			gseThread					*gseEngineThread;
			gseScriptEngine				*scriptEngine;

			bool	ioOperation;	//	OpenGSE is loading or saving a file. Display a static image for now.
			bool	paused;
			bool	shutdown;
			bool	picknplaceMode;
		public:
						//!	COpenGSE constructor, pass a Windows handle to the control that you 
						//! wish to use as a 'rendering context' for OpenGSE, this can be any 
						//! windows control.
						COpenGSE	( HWND hWindow );

//				irr::s32	readConfig	( const irr::core::stringc filePathName );
//				irr::s32	writeConfig	( const irr::core::stringc filePathName );

				irr::scene::ISceneManager	*getSceneManager	( void )	{	if ( !this ) return( NULL ); else return( sceneMgr ); };
#if	defined( USE_INTEL_TERRAIN )
				gseTerrainManager			*getTerrainManager	( void )	{	if ( !this ) return( NULL ); else return( terrainMgr ); };
#endif
#if	defined( USE_SHIFT_TERRAIN )
				gseTerrain	*getTerrainManager ( void ) { if ( !this ) return( NULL ); else return( terrainMgr ); };
#endif
#if	defined( USE_PAGED_TERRAIN )
				CTerrainPager	*getTerrainManager	( void ) { if ( !this ) return( NULL ); else return( terrainMgr ); };
#endif

				//!	Returns true/false on whether the engine is in pick and place mode or normal render mode.
				// The difference is that that object can be picked and moved, for example by an editor when
				// in pick and place mode (true).
				bool	getEngineMode	( void )	{	return( picknplaceMode ); };

				void	setPauseState	( bool flag )
				{
					paused = flag;
				};

				//! Destructor for OpenGSE, cleans up Irrlicht, OpenGSE and removes itself from
				//! the associated windows control that OpenGSE was using as a rendering context.
				virtual	~COpenGSE	( void );

				irr::scene::ICameraSceneNode *getCamera	( void )
				{
					if ( !cameraNode )
						return( NULL );
					return( cameraNode );
				};

				virtual	irr::s32	loadSceneFromFile	( const irr::core::stringc filePathName );
				virtual	irr::s32	saveSceneToFile		( const irr::core::stringc filePathName );

				virtual irr::s32	importIrrlichtScene	( const irr::core::stringc filePathName );
				virtual	irr::s32	exportIrrlichtScene	( const irr::core::stringc filePathName );

				//!	Returns the current device from Irrlicht that is being used by OpenGSE.
				irr::IrrlichtDevice	*getDevice		( void )	{	return( mDevice ); };
				//!	Returns the current Windows handle (HWND) for the window, or control, that is
				//! being used by OpenGSE to display the contents of the scene from the current
				//! camera.
				HWND				getWindowHandle	( void )	{	return( m_hWindow ); };

				//!	Handle the Windows message loop.
				LRESULT CALLBACK	WndProc	( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );

				//! Render the contents of the current OpenGSE 'scene'.
				void	Render		( void );

				//!	Returns a status flag indicating whether OpenGSE is shutting down or not.
				bool	isShuttingDown	( void )	{	if ( !this ) return( true ); else return( shutdown );	};
				//!	Returns a status flag indicating whether OpenGSE is paused or not.
				bool	isPaused	( void )	{	if ( !this ) return( true ); else return( paused ); };

				//	Add methods, these add existing objects into the engine.

				//	Create methods, these create new objects and add them into the engine.

				//!	Create a triangle meta selector for objects in the scene node, and it's children, for the
				//! given camera.
				void	createCameraCollisionResponse	( const irr::scene::ICameraSceneNode *camera, const irr::scene::ISceneNode *root );

				//!	Creates an object and inserts it into the current scene that is in use by OpenGSE.
				gseObject	*createObject	( irr::core::stringc objectType, irr::scene::ISceneNode *parent = NULL, irr::s32 id = -1,
					const irr::core::vector3df &position = irr::core::vector3df( 0, 0, 0 ),
					const irr::core::vector3df &rotation = irr::core::vector3df( 0, 0, 0 ),
					const irr::core::vector3df &scale = irr::core::vector3df( 1.0, 1.0, 1.0 ) );


				//	Remove methods, these remove an object, or objects from the engine.
//				void	removeObject		( const irr::core::stringc object_name );
//				void	removeObject		( const irr::u32 object_ident );
//				void	removeAllObjects	( void );

#if	defined( _DEBUG ) && !defined( NDEBUG )
				irr::s32	constructDebugScene	( irr::s32 sceneId = -1 );
#endif

	};
The constructor for the class which allows for Irrlicht to be made to use a specific windows control
which can be anything from a button, panel or full window.

Code: Select all

// This is the constructor of a class that has been exported, see OpenGSE.h for the class definition
// Based on the handle of the control passed to it start initialising the internal properties for
// OpenGSE along with making sure that Irrlicht (and friends) are also initialised, this method
// also associates a custom windows message handler with the passed control to allow for OpenGSE
// to be able to respond to any appropriate windows messages, including custom ones.
COpenGSE::COpenGSE( HWND hWindow )
{
	logMessage( "COpenGSE construction taking place ... \n" );
	cameraNode = NULL;

	paused = true;
	shutdown = false;

#if	defined( _USE_INTEL_TERRAIN )
	terrainMgr = NULL;
#endif
#if	defined( USE_SHIFT_TERRAIN )
	terrainMgr = NULL;
#endif
#if	defined( USE_PAGED_TERRAIN )
	terrainMgr = NULL;
#endif

	if ( !gseEngine )
		gseEngine = this;

	if ( !hWindow ) {
		gseEngine = NULL;
		logMessage( "Bad window handle supplied to COpenGSE constructor. Create fullscreen window to compensate.\n" );
		ErrorExit( (LPTSTR)L"COpenGSE, bad window handle given to OpenGSE.", 1 );
	}

	//	Initialise Irrlicht to use the window handle provided.
	RECT	rc;
	GetWindowRect( hWindow, &rc );

	m_hWindow = hWindow;

	//	Associate this instance of OpenGSE with the window handle that has been passed to it.
	SetWindowLongPtr( m_hWindow, GWLP_USERDATA, (LONG)this );

	//	Construct the event receiver and data serialiazer that are used when dealing with Irrlicht.
	logMessage( "Constructing event receiver ... " );
	eventReceiver = new gseEventReceiver;
	if ( eventReceiver )
		logMessage( "OK.\n" );
	else
		logMessage( "FAILED.\n" );

	logMessage( "Constructing data serializer ... " );
	dataSerializer = new gseDataSerializer;
	if ( dataSerializer )
		logMessage( "OK.\n" );
	else
		logMessage( "FAILED.\n" );

	irr::SIrrlichtCreationParameters createParams;

	//	Construct the Irrlicht device to use the supplied window control and resize to fit it.
	memset( (void*)&createParams, 0, sizeof( irr::SIrrlichtCreationParameters ) );
	createParams.WindowId = reinterpret_cast<void*>(hWindow);
	createParams.DriverType = irr::video::EDT_DIRECT3D9;
	createParams.WindowSize = irr::core::dimension2d<irr::s32>(rc.right, rc.bottom);
	createParams.EventReceiver = eventReceiver;
	createParams.Vsync = true;

	logMessage( "Constructing Irrlicht device ... " );
	mDevice = irr::createDeviceEx( (const irr::SIrrlichtCreationParameters)createParams );
	if ( !mDevice ) {
		logMessage( "FAILED.\n" );
		ErrorExit( (LPTSTR)L"COpenGSE, unable to construct Irrlicht device.", 2 );
	} else
		logMessage( "OK.\n" );

	//	Store a copy of the device being used by Irrlicht and also store a copy of the pointers
	// to the current gui and scene manager. Also tell the scene manager that we are using our
	// own version of the event receiver class to deal with messages passing through Irrlicht.
	mDevice->setEventReceiver( eventReceiver );
	mDevice->setResizeAble( true );

	envMgr = mDevice->getGUIEnvironment();
	sceneMgr = mDevice->getSceneManager();

	envMgr->setUserEventReceiver( eventReceiver );

	defaultFont = envMgr->getBuiltInFont();

	//	Save the original window proc.
	wndProc = (WNDPROC)GetWindowLongPtr( m_hWindow, GWLP_WNDPROC );
	//	Take over the operations of the window message handler.
	SetWindowLongPtr( m_hWindow, GWLP_WNDPROC, (LONG)(gseWndProc) );

	//	Construct the associated PhysX and AI modules to the engine.
	memset( (void*)&sceneDesc, 0, sizeof( IrrPhysx::SSceneDesc ) );
	physxMgr = IrrPhysx::createPhysxManager( mDevice, (const IrrPhysx::SSceneDesc)sceneDesc );
	aiMgr = IrrAI::createAIManager( mDevice );

	//	Here would be construct any local threads for OpenGSE and set them to work.
	logMessage( "Constructing engine thread ... " );
	gseEngineThread = new gseThread( (LPVOID)&threadFunc, (LPVOID)this, false );
	if ( gseEngineThread )
		logMessage( "OK.\n" );
	else
		logMessage( "FAILED.\n" );

	//	Broadcast a message to the parent window control to show itself and then
	// to the control itself to paint itself.
	SendMessage( GetParent( m_hWindow ), SW_SHOW, NULL, NULL );
	SendMessage( m_hWindow, WM_PAINT, NULL, NULL );

	//	DONT USE THE SCRIPT ENGINE AT PRESENT IT HAS NO WAY OF DOING OVERLAPPED I/O ON
	// THE CONSOLE AND THIS DOESNT ALLOW OPENGSE TO FUNCTION CORRECTLY.
	scriptEngine = NULL;

	//	Set the internal variable to say we are not paused and everything should render/run.
	// This must be done last.
	paused = false;

	logMessage( "COpenGSE finished.\n" );

}
Oops, nearly forgot the rendering method :oops:

Code: Select all

//	This does the rendering, perhaps this should be in it's own thread of sorts.
// Promoted to threading behaviour, the method (above) threadFunc does nothing
// more than call this method, repeatedly as part of a thread.
//	+	display the frame rate.
void	COpenGSE::Render( void )
{
	static	int	lastFPS = -1;
	irr::core::stringw str;// = L"OpenGSE [ ";

	str = L"OpenGSE [ ";
	str += mDevice->getVideoDriver()->getName();
	str += " ] ";

	if ( paused ) {
		str += "PAUSED";
	}

	if ( shutdown ) {
		str += "SHUTDOWN";
	}

	if ( !paused && !shutdown ) {
		physxMgr->simulate( 1.0f / 60 );
		physxMgr->fetchResults();
		aiMgr->update( (irr::s32)(1.0f / 60) );
		str += " ] FPS:";
		str += mDevice->getVideoDriver()->getFPS();
	}

	if ( mDevice && !paused ) {
		mDevice->getTimer()->tick();
		mDevice->getVideoDriver()->beginScene();
		try {
			if ( sceneMgr && cameraNode )
				sceneMgr->drawAll();	// 3d stuffs
		}
		catch (...) {
			logMessage( "Exception during rendering.\n" );
		}
		envMgr->drawAll();		// 2d gui stuffs.
		if ( defaultFont ) {		//	2d hud/debug info.
			defaultFont->draw( (const wchar_t*)str.c_str(), core::rect<s32>(0,0,342,224), video::SColor(255,255,255,255), false, false );
			defaultFont->draw( (const wchar_t*)L"Testing the frame rates for OpenGSE.", irr::core::rect<s32>(0,10,150,40), irr::video::SColor(127,127,255,255) );
		}
#if	defined( USE_INTEL_TERRAIN )
		if ( terrainMgr )
			terrainMgr->Render(cameraNode->getAbsoluteTransformation());
#endif
#if	defined( USE_SHIFT_TERRAIN )
		if ( terrainMgr )
			terrainMgr->render();
#endif
#if	defined( USE_PAGED_TERRAIN )
//		if ( terrainMgr )
//			terrainMgr->render();
#endif
		mDevice->getVideoDriver()->endScene();
	}
}
Screen shot of C++ .NET application using the dll.

http://www.flickr.com/photos/56603839@N07/5226983623/


Extra info,

The engine also takes over control of handling the windows messages from the application that
are directed towards that control, ie COpenGSE has it's own windows messaging handler.

No I am *NOT* using the device->run() method rather the tick() method each render loop and
making sure the framerate is synched to the current modes refresh rate.

Have fun, and may the source be with you.
Cobra El Diablo
Post Reply