From ced66e610c6c740f1388e8205bfb3c48fcf22794 Mon Sep 17 00:00:00 2001 From: cutealien Date: Thu, 26 Aug 2021 21:57:27 +0000 Subject: [PATCH] Merging r6196 through r6248 from trunk to ogl-es branch git-svn-id: svn://svn.code.sf.net/p/irrlicht/code/branches/ogl-es@6249 dfc29bdd-3216-0410-991c-e03cc46cb475 --- changes.txt | 23 +- doc/release_checklist.txt | 21 +- examples/01.HelloWorld/tutorial.html | 625 ++--- examples/02.Quake3Map/tutorial.html | 389 +-- examples/03.CustomSceneNode/tutorial.html | 431 ++- examples/04.Movement/tutorial.html | 396 +-- examples/05.UserInterface/tutorial.html | 431 ++- examples/06.2DGraphics/tutorial.html | 371 +-- examples/07.Collision/tutorial.html | 515 ++-- examples/08.SpecialFX/tutorial.html | 488 ++-- examples/09.Meshviewer/tutorial.html | 395 +-- examples/10.Shaders/tutorial.html | 776 ++---- examples/11.PerPixelLighting/tutorial.html | 708 ++--- examples/12.TerrainRendering/tutorial.html | 331 ++- examples/13.RenderToTexture/tutorial.html | 453 ++- examples/14.Win32Window/tutorial.html | 457 ++- examples/15.LoadIrrFile/tutorial.html | 339 ++- examples/16.Quake3MapShader/tutorial.html | 206 ++ examples/17.HelloWorld_Mobile/main.cpp | 21 +- examples/17.HelloWorld_Mobile/tutorial.html | 207 ++ examples/18.SplitScreen/main.cpp | 3 + examples/18.SplitScreen/tutorial.html | 236 ++ examples/19.MouseAndJoystick/tutorial.html | 205 ++ examples/20.ManagedLights/tutorial.html | 210 ++ examples/21.Quake3Explorer/tutorial.html | 218 ++ examples/22.MaterialViewer/tutorial.html | 206 ++ examples/23.SMeshHandling/tutorial.html | 209 ++ examples/24.CursorControl/tutorial.html | 205 ++ examples/25.XmlHandling/main.cpp | 4 +- examples/25.XmlHandling/tutorial.html | 219 ++ examples/26.OcclusionQuery/tutorial.html | 207 ++ examples/27.PostProcessing/tutorial.html | 209 ++ examples/28.CubeMapping/tutorial.html | 213 ++ examples/30.Profiling/tutorial.html | 209 ++ include/EGUIAlignment.h | 3 +- include/ICameraSceneNode.h | 2 +- include/IGUIEnvironment.h | 6 +- include/IGeometryCreator.h | 20 + include/IIndexBuffer.h | 2 +- include/ILightManager.h | 3 +- include/IRenderTarget.h | 51 +- include/ISceneManager.h | 2 +- include/ITexture.h | 8 + include/IVertexBuffer.h | 1 + include/IVideoDriver.h | 4 +- include/IrrCompileConfig.h | 2 +- include/SIrrCreationParameters.h | 7 +- include/SLight.h | 2 +- include/SVertexManipulator.h | 1 + include/SceneParameters.h | 2 + include/fast_atof.h | 7 + include/irrArray.h | 43 +- include/leakHunter.h | 3 +- include/line2d.h | 3 +- include/path.h | 4 +- scripts/doc/irrlicht/doxygen.cfg | 4 +- scripts/doc/irrlicht/doxygen_tutorial.cfg | 2442 +++++++++++++++++ scripts/doc/irrlicht/makedocumentation.sh | 9 + scripts/doc/irrlicht/maketut.sed | 12 +- scripts/doc/irrlicht/maketutorial.bat | 8 +- scripts/doc/irrlicht/maketutorial.sh | 36 +- scripts/doc/irrlicht/tut_end.html | 15 +- scripts/doc/irrlicht/tut_head.html | 274 +- source/Irrlicht/CD3D9Driver.cpp | 24 +- source/Irrlicht/CD3D9Driver.h | 3 - source/Irrlicht/CD3D9RenderTarget.cpp | 78 +- source/Irrlicht/CD3D9RenderTarget.h | 4 +- source/Irrlicht/CD3D9Texture.cpp | 10 +- source/Irrlicht/CD3D9Texture.h | 6 + source/Irrlicht/CGLXManager.cpp | 2 +- source/Irrlicht/CGUIEnvironment.cpp | 7 +- source/Irrlicht/CGUIEnvironment.h | 2 +- source/Irrlicht/CGUIListBox.cpp | 3 + source/Irrlicht/CGUIModalScreen.cpp | 28 +- source/Irrlicht/CGUIModalScreen.h | 16 + source/Irrlicht/CGeometryCreator.cpp | 136 +- source/Irrlicht/CGeometryCreator.h | 4 + source/Irrlicht/CImageLoaderJPG.cpp | 15 +- source/Irrlicht/CImageLoaderJPG.h | 3 - source/Irrlicht/CIrrDeviceConsole.h | 2 +- source/Irrlicht/CIrrDeviceLinux.cpp | 25 +- source/Irrlicht/CIrrDeviceLinux.h | 68 +- source/Irrlicht/CIrrDeviceSDL.cpp | 2 +- source/Irrlicht/CIrrDeviceSDL.h | 2 +- source/Irrlicht/CIrrDeviceWin32.cpp | 4 +- source/Irrlicht/CIrrDeviceWin32.h | 2 +- source/Irrlicht/CNullDriver.cpp | 14 +- source/Irrlicht/COBJMeshFileLoader.cpp | 6 +- source/Irrlicht/COBJMeshWriter.cpp | 27 +- source/Irrlicht/COpenGLCoreRenderTarget.h | 70 +- source/Irrlicht/COpenGLExtensionHandler.cpp | 10 +- source/Irrlicht/COpenGLExtensionHandler.h | 3 + source/Irrlicht/CSceneManager.cpp | 4 +- source/Irrlicht/CSceneManager.h | 2 +- .../CSceneNodeAnimatorFollowSpline.cpp | 26 +- .../Irrlicht/CSceneNodeAnimatorFollowSpline.h | 3 +- source/Irrlicht/CSoftwareDriver2.cpp | 12 +- source/Irrlicht/CSoftwareTexture.cpp | 26 +- source/Irrlicht/CSoftwareTexture.h | 2 +- source/Irrlicht/CSoftwareTexture2.cpp | 24 +- source/Irrlicht/CSoftwareTexture2.h | 2 +- source/Irrlicht/CXMLReaderImpl.h | 16 +- source/Irrlicht/EProfileIDs.h | 1 + source/Irrlicht/Irrlicht10.0.vcxproj | 9 +- source/Irrlicht/Irrlicht10.0.vcxproj.filters | 13 +- source/Irrlicht/Makefile | 24 +- source/Irrlicht/SoftwareDriver2_helper.h | 2 +- tests/tests-last-passed-at.txt | 2 +- tools/GUIEditor/CGUIAttribute.h | 1 - tools/GUIEditor/CGUIAttributeEditor.cpp | 1 - tools/GUIEditor/CGUIEditWorkspace.cpp | 41 +- tools/GUIEditor/CGUIEditWorkspace.h | 3 + 112 files changed, 9559 insertions(+), 4343 deletions(-) create mode 100644 examples/16.Quake3MapShader/tutorial.html create mode 100644 examples/17.HelloWorld_Mobile/tutorial.html create mode 100644 examples/18.SplitScreen/tutorial.html create mode 100644 examples/19.MouseAndJoystick/tutorial.html create mode 100644 examples/20.ManagedLights/tutorial.html create mode 100644 examples/21.Quake3Explorer/tutorial.html create mode 100644 examples/22.MaterialViewer/tutorial.html create mode 100644 examples/23.SMeshHandling/tutorial.html create mode 100644 examples/24.CursorControl/tutorial.html create mode 100644 examples/25.XmlHandling/tutorial.html create mode 100644 examples/26.OcclusionQuery/tutorial.html create mode 100644 examples/27.PostProcessing/tutorial.html create mode 100644 examples/28.CubeMapping/tutorial.html create mode 100644 examples/30.Profiling/tutorial.html create mode 100644 scripts/doc/irrlicht/doxygen_tutorial.cfg diff --git a/changes.txt b/changes.txt index be43b0ba..6ee71071 100644 --- a/changes.txt +++ b/changes.txt @@ -9,6 +9,26 @@ Changes in ogl-es (not yet released - will be merged with trunk at some point) -------------------------- Changes in 1.9 (not yet released) +- Add equals and set_data functions to core::array for easier working with blocks of data. +- SIrrlichtCreationParameters::IgnoreInput set to false works again on X11. + Thanks @ Victor Gaydov for report + patch + very good test cases! (bug #401) + This had been broken since Irrlicht 1.6 +- Add steer parameter to CSceneNodeAnimatorFollowSpline which allows rotating node toward direction of movement. + Thanks @ Bate for the patch (patch #175) +- Add a workaround for XWarpPointer bug that causes mouse to jump when users have set a Coordinate Transformation Matrix for their mouse on X11. + This was mentioned in bug #450 by vikaig. + The fix needs compiling with _IRR_LINUX_X11_XINPUT2_ enabled (so far disabled by default) +- Add IGeometryCreator::createTorusMesh to create donuts. +- Don't try loading broken image files twice with same loader anymore. +- Make CImageLoaderJPG thread safe. Thanks @ Edoardo Lolletti for report and patch (patch #324) +- Add ETCF_SUPPORT_VERTEXT_TEXTURE flag which can be used to enable vertex texture sampling support in Direct3D 9. + Note that this was enabled for a long time in 1.9 svn, but is now disabled by default. +- CGUIListBox now serializes the state of "Selected". Feature wish by chronologicaldot (http://irrlicht.sourceforge.net/forum/viewtopic.php?f=2&t=52719) +- Bugfix: Changing focus with tab-keys now also work when elements are inside a modal screen. +- COBJMeshFileLoader using a bit more exact color conversion for 0-1 to 0-255 range (same now as SColorf::toSColor uses). +- Speedup for COBJMeshWriter +- Add blinkMode parameter to IGUIEnvironment::addModalScreen, so blinking can be suppressed +- Speedup: Avoid string copy in CXMLReaderImpl::getAttributeByName - Fix bug in rect::clipAgainst that had caused rects completely outside to the left-top of the rect to be clipped against ending up with both corners outside. It still worked for UI in most cases as the resulting rectangle still had an area of 0. - Add getAlign functions to IGUIElement @@ -84,7 +104,7 @@ Changes in 1.9 (not yet released) - Drivers can now try to create textures from images in more exotic color formats (like floating point formats). It depends on the driver how much that works (so far mainly OpenGL can handle it somewhat). - Fix OpenGL to no longer switch colors red and blue in 24-bit RGB format. But warnings added to documentation to avoid 24-bit textures as they are generally just trouble. - No longer try to convert ECF_R5G6B5 to ECF_A1R5G5B5 on OpenGL (just made texture-loading seem to fail). -- Add flag SIrrlichtCreationParameters.WindowResizable. Mainly to work around troubles with SDL+OpenGL on some platforms where resizing later can be tricky/impossible. +- Add parameter SIrrlichtCreationParameters.WindowResizable. Mainly to work around troubles with SDL+OpenGL on some platforms where resizing later can be tricky/impossible. - Add operator[] to vector2d and vector3d - Bugfix: IrrlichtDevice::isWindowMinimized no longer returns true when it's maximized on Windows. - Ignore degenerated faces in obj file loader when they would generate triangles where 2 vertices use identical indices. @@ -318,6 +338,7 @@ Changes in 1.9 (not yet released) -------------------------- Changes in 1.8.5 + - Update script to generate tutorial.html's in example folders. Add missing ones. Update existing ones. Thanks @Guillian J for noticing those were outdated. - Update libpng to 1.6.37 (from 1.6.23) - Fix CIrrDeviceSDL::getVideoModeList which didn't return video modes before. Thx @kas1e for report and patch. - CIrrDeviceMacOSX now sets the SEvent.MouseInput Shift and Control values on mouse events like the other devices. Thanks @ Zero King for patch (#321) diff --git a/doc/release_checklist.txt b/doc/release_checklist.txt index 840e7728..42eae88a 100644 --- a/doc/release_checklist.txt +++ b/doc/release_checklist.txt @@ -1,12 +1,12 @@ -Checklist for Irrlicht developers for doing releases. +Checklist for Irrlicht developers doing releases. + +Note: Generally the more platforms, compilers, settings you can test the better. Ask for help for platforms which you don't own. - PRE-BUILD TESTS: - - Run tests in the tests folder -- - Compile and run all examples for testing (preferably on all platforms, -compilers, settings ... until you are certain enough stuff works sufficiently). -Ask for help for platforms which you don't own. -- - Compile the tools on all platforms you have. Note that some tools are in the buildall-examples VS project files on Windows, but on Linux -command line you have to compile them individually. +- - Compile and run examples. +- - Compile and run the tools. Note that some tools are in the buildall-examples VS project files on Windows, + but on Linux command line you have to compile them individually. - VERSION UPDATES: - - check IRRLICHT_SDK_VERSION (in IrrCompileConfig.h) @@ -15,13 +15,16 @@ command line you have to compile them individually. - - Add new release information (date+version-number) in changes.txt - - go through folders if other .txt files still make sense (things change and updating those files tends to be forgotten) +- DOCUMENTATION UPDATES: +- - run makedocumentation.sh in scripts\doc\irrlicht +- - run maketutorial.sh in scripts\doc\irrlicht (commit changed tutorial.html's) + - BUILDING THE RELEASE + (TBD - should we still release dll's? Newer and older VS builds are no longer compatible anyway) - - run a clean build for buildAllExamples in the examples folder with the -target compiler for 32-bit and for release (preferably oldest supported VS -compiler, otherwise oldest you have still installed) + target compiler for 32-bit and for release (old VS compiler - so far VS2010) - - when possible compile the dll for MinGW on Windows (in release and with -s for smaller size) - - when possible compile the dll for 64 bit (again with Visual Studio and release) -- - run makedocumentation in scripts\doc\irrlicht - - create a target directory, like irrlicht-1.8.1 for example - - svn export to the target directory - - copy the subfolders of doctemp into the doc folder of the target directory diff --git a/examples/01.HelloWorld/tutorial.html b/examples/01.HelloWorld/tutorial.html index e46c9542..916e554e 100644 --- a/examples/01.HelloWorld/tutorial.html +++ b/examples/01.HelloWorld/tutorial.html @@ -1,394 +1,231 @@ - - -Irrlicht Engine Tutorial - - - - -
- - - - - - - - - -
Tutorial 1.HelloWorld
-

This Tutorial shows how to set up the IDE for using the - Irrlicht Engine and how to write a simple HelloWorld program - with it. The program will show how to use the basics of - the VideoDriver, the GUIEnvironment and the SceneManager.
- The result of this example will look like this:

-


-

-
-
- - - - - - -
Setting up the - IDE
-
-

To use the engine, we will have to include - the header file <irrlicht.h>, which can be found - in the Irrlicht Engine SDK directory \include. To let - the compiler find this header file, the directory where - it is located should be specified somewhere. This is different - for every IDE and compiler. I will explain how to do this - in Microsoft Visual Studio C++ 6.0 and .NET:

- -
-
    -
  • -
    If you use Version 6.0, select the Menu - Extras -> Options. Select the directories tab, and - select the 'Include' Item in the combo box. Add the - \include directory of the Irrlicht Engine folder to - the list of directories. Now the compiler will find - the Irrlicht.h header file. We also need the location - of irrlicht.lib to be listed, so select the 'Libraries' - tab and add the \lib\VisualStudio directory.
    -
    -   
    -  
    - -
    -
  • -
  • If your IDE is Visual Studio .NET, select Tools -> - Options. Select the Projects entry and then select VC++ - directories. Select 'show directories for include files' - in the combo box, and add the \include directory of the - Irrlicht Engine folder to the list of directories so the - compiler will find the Irrlicht.h header file. We also - need the irrlicht.lib to be found, so select 'show directories - for Library files' and add the \lib\VisualStudio directory.
    -
    - -
    -
  • -
- -

 

-
-
- - - - - - - - -
Lets start!
-
-
-
-

After we have set up the IDE, the compiler will know - where to find the Irrlicht Engine header files so - we can include it now into our code.

- - - - - -
#include <irrlicht.h>
-

In the Irrlicht Engine, everything can be found in - the namespace 'irr'. So if you want to use a class - of the engine, you'll have to type an irr:: before - the name of the class. For example, to use the IrrlichtDevice, - write: irr::IrrlichtDevice. To avoid having to put - irr:: before of the name of every class, we tell the - compiler that we use that namespace.

- - - - - -
using namespace irr;
-

There are 5 sub-namespaces in the Irrlicht Engine. - Take a look at them: you can read a detailed description - of them in the documentation by clicking on the top - menu item 'Namespace - List'. To keep this example simple, we don't want - to have to specify the name spaces, Hence:

- - - - - -
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;
-

To be able to use the Irrlicht.DLL file, we need - to link with the Irrlicht.lib. We could set this option - in the project settings, but to make it easy we use - a pragma comment:

- - - - - -
#pragma comment(lib, "Irrlicht.lib")
-

Now the main method: to keep this example simple - we use int main(), which can be used on any platform. - However, on Windows platforms, we could also use the - WinMain method if we would want to get rid of the - console window which pops up when starting a program - with main().

- - - - - -
int main()
{
-

The most important function of the engine is the - 'createDevice' function. The Irrlicht Device, which - is the root object for doing everything with the engine, - can be created with it. createDevice() has 7 parameters:

-
-
    -
  • - -
    deviceType: Type of the device. This can currently - be the Null device, the Software device, Direct3D8, Direct3D9, - or OpenGL. In this example we use EDT_SOFTWARE, but, to try - them out, you might want to change it to EDT_NULL, EDT_DIRECT3D8, - EDT_DIRECT3D9 or EDT_OPENGL.
    -
  • -
  • -
    windowSize: Size of the window or - full screen mode to be created. In this example - we use 512x384.
    - -
  • -
  • -
    bits: Number of bits per pixel when - in full screen mode. This should be 16 or 32. This - parameter is ignored when running in windowed mode.
    -
  • -
  • -
    fullscreen: Specifies if we want - the device to run in full screen mode or not.
    -
  • -
  • stencilbuffer: Specifies if we want to use the stencil - buffer for drawing shadows.
  • - -
  • vsync: Specifies if we want to have vsync enabled. - This is only useful in full screen mode.
  • -
  • -
    eventReceiver: An object to receive - events. We do not want to use this parameter here, - and set it to 0.
    -
  • -
- - - - - -
IrrlichtDevice *device =
createDevice(EDT_SOFTWARE, dimension2d<s32>(512, 384), 16,
false, false, false, 0);
-

Now we set the caption of the window to some nice text. - Note that there is a 'L' in front of the string: the - Irrlicht Engine uses wide character strings when displaying - text.

- - - - - -
device->setWindowCaption(L"Hello World! - Irrlicht Engine Demo");
-

Now we store a pointer to the video driver, the SceneManager, - and the graphical user interface environment so that - we do not always have to write device->getVideoDriver(), - device->getSceneManager(), and device->getGUIEnvironment().

- - - - - -
IVideoDriver* driver = device->getVideoDriver();
ISceneManager* smgr = device->getSceneManager();
IGUIEnvironment* guienv = device->getGUIEnvironment();
-

We add a hello world label to the window using the - GUI environment. The text is placed at the position - (10,10) as top left corner and (200,22) as lower right - corner.

- - - - - -
guienv->addStaticText(L"Hello World! This is the Irrlicht Software engine!",
rect<s32>(10,10,200,22), true);
-

To display something interesting, we load a Quake 2 - model and display it. We only have to get the Mesh from - the Scene Manager with getMesh() and add a SceneNode - to display the mesh with addAnimatedMeshSceneNode(). - Instead of loading a Quake2 file (.md2), it is also - possible to load a Maya object file (.obj), a complete - Quake3 map (.bsp), or a Milshape file (.ms3d).
- By the way, that cool Quake 2 model called sydney.md2 - was modelled by Brian Collins.

- - - - - -
IAnimatedMesh* mesh = smgr->getMesh("../../media/sydney.md2");
IAnimatedMeshSceneNode* node = smgr->addAnimatedMeshSceneNode( mesh );
-

To make the mesh look a little bit nicer, we change - its material a little bit: we disable lighting because - we do not have a dynamic light in here and the mesh - would be totally black. Then we set the frame loop so - that the animation is looped between the frames 0 and - 310. Then, at last, we apply a texture to the mesh. - Without it the mesh would be drawn using only a solid - color.

- - - - - -
if (node)
{
node->setMaterialFlag(EMF_LIGHTING, false);
node->setFrameLoop(0, 310);
node->setMaterialTexture( 0, driver->getTexture("../../media/sydney.bmp") );
}
-
-

To look at the mesh, we place a camera into 3d space - at the position (0, 10, -40). The camera looks from - there to (0,5,0).

- - - - - -
smgr->addCameraSceneNode(0, vector3df(0,30,-40), vector3df(0,5,0));
-

Ok. Now that we have set up the scene, let's draw everything: - we run the device in a while() loop until the device - does not want to run any more. This would be when the - user closes the window or presses ALT+F4 in Windows.

- - - - - -
while(device->run())
{
-

Everything must be drawn between a beginScene() and - an endScene() call. The beginScene clears the screen - with a color and also the depth buffer, if desired. - Then we let the Scene Manager and the GUI environment - draw their content. With the endScene() call, everything - is presented on the screen.

- - - - - - -
	driver->beginScene(true, true, SColor(255,100,101,140));
- smgr->drawAll(); - guienv->drawAll();
-
	driver->endScene();
-}
-

After we are finished, we have to delete the Irrlicht - Device created earlier with createDevice(). With the - Irrlicht Engine, you should delete all objects you created - with a method or function that starts with 'create'. - The object is deleted simply by calling ->drop(). - See the documentation - for more information.

- - - - - -
	device->drop();
return 0; -}
-

That's it. Compile and run.

-

 

-
-
-
-
- - - - - - - -
Possible Errors - or Problems
-
-
-

Visual Studio
- - While trying to compile the tutorial, if you get the - error:

- - - - -
fatal - error C1083: Cannot open include file: 'irrlicht.h': - No such file or directory
-

Solution: You may have set the include directory improperly - in the Visual Studio options. See above - for information on setting it.

- - - - - -
LINK - : LNK6004: HelloWorld.exe not found or not built - by the last incremental link; performing full link
- LINK : fatal error LNK1104: cannot open file "Irrlicht.lib"
- Error executing link.exe
-

Solution: You may have set the library directory improperly. - See above for information on - setting it.
- -
-

-

Compiler independent problems
-
If the tutorial compiles successfully but gives - the error:

- - - - - -
This - application has failed to start because Irrlicht.dll - was not found. Re-installing the application may - fix this problem
-

Solution: You may have forgotten to copy the Irrlicht.dll - file from Irrlicht\bin\VisualStudio to the directory - the tutorial's project file is in.

- If the tutorial compiles and runs successfully but produces - errors in the console like:
-
- - - - - -
Could - not load mesh, because file could not be opened.: - ../media/sydney.md2
-

Or:

- - - - - -
Could - not open file of texture: stones.jpg
-
Could not load texture: stones.jpg
-

Solution: The file listed in the error message cannot - be found. Ensure that the directory specified in the - main.cpp exists and is where the file is located.
-

-
-
-
-

 

- - + + + + + + + +Tutorial 1: HelloWorld + + + + +
+ + + +
+
+
+
Tutorial 1: HelloWorld
+
+
+
+001shot.jpg +
+

This tutorial shows how to set up the IDE for using the Irrlicht Engine and how to write a simple HelloWorld program with it. The program will show how to use the basics of the VideoDriver, the GUIEnvironment, and the SceneManager. Microsoft Visual Studio is used as an IDE, but you will also be able to understand everything if you are using a different one or even another operating system than Windows.

+

You have to include the header file <irrlicht.h> in order to use the engine. The header file can be found in the Irrlicht Engine SDK directory include. To let the compiler find this header file, the directory where it is located has to be added in your project as include path. This is different for every IDE and compiler you use. Let's explain shortly how to do this in Visual Studio 2010:

+
    +
  • In Visual Studio 2010 select the Menu Project -> Properties. Select the "C/C++" - "General" option, and select the "Additional Include Directories". Add the include directory of the Irrlicht engine folder to the list of directories. Now the compiler will find the irrlicht.h header file. We also need the irrlicht.lib to be found, so select "Linker" - "General" and add the lib/Win64-visualStudio or lib/Win32-visualStudio directory to "Additional Library Directories". Which of the 2 Irrlicht versions you chose depends on the target platform for your application (win32 or x64). In your project properties you can see what your active solution platform is, you can use the same one for Irrlicht.
  • +
+

To be able to use the Irrlicht.DLL file, we need to link with the Irrlicht.lib. In most IDE's you have to add irrlicht.lib (or irrlicht.a or irrlicht.so on Linux) to your Linker input files.

+

For VisualStudio we can be lazy and use the pragma comment lib. We also want to get rid of the console window, which pops up when starting a program with main() (instead of WinMain). This is done by the second pragma. We could also use the WinMain method, though losing platform independence then.

#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
#endif

That's it. With your IDE set up like this, you will now be able to develop applications with the Irrlicht Engine.

+

Lets start!

+

After we have set up the IDE, the compiler will know where to find the Irrlicht Engine header files so we can include it now in our code.

#include <irrlicht.h>

That header just adds the getExampleMediaPath tool-functions to help locating the media we need. More about that later below.

#include "exampleHelper.h"

In the Irrlicht Engine, everything can be found in the namespace 'irr'. So if you want to use a class of the engine, you have to write irr:: before the name of the class. For example to use the IrrlichtDevice write: irr::IrrlichtDevice. To get rid of the irr:: in front of the name of every class, we tell the compiler that we use that namespace from now on, and we will not have to write irr:: anymore. Note that you never should do that in headers - otherwise you will pollute the namespace of every file including such a header. So in headers always write out the full names including all namespaces.

using namespace irr;

There are 5 sub namespaces in the Irrlicht Engine. Take a look at them, you can read a detailed description of them in the documentation by clicking on the top menu item 'Namespace List' or by using this link: http://irrlicht.sourceforge.net/docu/namespaces.html Like the irr namespace, we do not want these 5 sub namespaces now, to keep this example simple. Hence, we tell the compiler again that we do not want always to write their names.

using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

This is the main method. We can now use main() on every platform.

int main()
{

The most important function of the engine is the createDevice() function. The IrrlichtDevice is created by it, which is the root object for doing anything with the engine. createDevice() has the following parameters:

+
    +
  • driverType: Type of the video driver. This can currently be the Null-device, one of the two software renderers, D3D9, or OpenGL. In this example we use EDT_BURNINGSVIDEO, but to try out, you might want to change it to EDT_SOFTWARE, EDT_NULL, EDT_DIRECT3D9, or EDT_OPENGL. Generally you will want to use OpenGL or Direct3D as they are using your graphic card for calculations instead of the CPU and are way faster (and usually better looking). We just use one of the software renderers here as it even works when your graphic card driver isn't set up for 3d support.
  • +
  • windowSize: Size of the Window or screen in FullScreenMode to be created. In this example we use 640x480.
  • +
  • bits: Amount of color bits per pixel. This should be 16 or 32. The parameter is often ignored when running in windowed mode. More commonly you would chose 32 bit, again we're just playing it safe.
  • +
  • fullscreen: Specifies if we want the device to run in fullscreen mode or windowed.
  • +
  • stencilbuffer: Specifies if we want to use the stencil buffer (you need it for drawing shadows).
  • +
  • vsync: Specifies if we want to have vsync enabled, this is only useful in fullscreen mode.
  • +
  • eventReceiver: An object to receive events. We do not want to use this parameter here, and set it to 0.
  • +
+

Always check the return value to cope with unsupported drivers, dimensions, etc.

IrrlichtDevice *device =
createDevice( video::EDT_BURNINGSVIDEO, dimension2d<u32>(640, 480), 16,
false, false, false, 0);
if (!device)
return 1;

Set the caption of the window to some nice text. Note that there is an 'L' in front of the string. The Irrlicht Engine uses wide character strings when displaying text.

device->setWindowCaption(L"Hello World! - Irrlicht Engine Demo");

Get a pointer to the VideoDriver, the SceneManager and the graphical user interface environment, so that we do not always have to write device->getVideoDriver(), device->getSceneManager(), or device->getGUIEnvironment().

IVideoDriver* driver = device->getVideoDriver();
ISceneManager* smgr = device->getSceneManager();
IGUIEnvironment* guienv = device->getGUIEnvironment();

We add a hello world label to the window, using the GUI environment. The text is placed at the position (10,10) as top left corner and (260,22) as lower right corner.

guienv->addStaticText(L"Hello World! This is Irrlicht with the burnings software renderer!",
rect<s32>(10,10,260,22), true);

Get a media path dedicated for your platform. Finding media files for your applications can be tricky. First you have 2 options - working with relative paths or working with absolute paths.

+

On Windows a common solution is that your installer will write a key into the registry with the absolute path of wherever the user installed the media. And in your application you read out that key from the registry. On Linux a common solution is to use config file which is placed in some fixed location (for example in a . file/folder in the user home).

+

But you can also work with relative paths - which is what we do here. There is a slight complication with relative paths as they are relative to your current working directory. And that depends on the way your application is started and it might change inside your application. But mostly it will be set to your executable on start so you can ignore that problem while developing.

+

When inside VisualStudio the current working directory is set to your project files location unless you overwrite Project properties - Debugging

    +
  • Working Directory. In Irrlicht examples the media folder is on most platforms ../../media which works for the examples as it's relative to our project files as well as to the binary (.exe) files.
  • +
+

Whatever you chose to find your base-folder for media - wrap it with some function and then you can improve the code to locate the media later on.

const io::path mediaPath = getExampleMediaPath();

To show something interesting, we load a Quake 2 model and display it. We get the Mesh from the Scene Manager with getMesh() and add a SceneNode to display the mesh with addAnimatedMeshSceneNode(). Check the return value of getMesh() to become aware of loading problems and other errors.

+

Instead of writing the filename sydney.md2, it would also be possible to load a Maya object file (.obj), a complete Quake3 map (.bsp) or any other supported file format. By the way, that cool Quake 2 model called sydney was modeled by Brian Collins.

IAnimatedMesh* mesh = smgr->getMesh(mediaPath + "sydney.md2");
if (!mesh)
{
device->drop();
return 1;
}
IAnimatedMeshSceneNode* node = smgr->addAnimatedMeshSceneNode( mesh );

To let the mesh look a little bit nicer, we change its material. We disable lighting because we do not have a dynamic light in here, and the mesh would be totally black otherwise. Then we set the frame loop, such that the predefined STAND animation is used. And last, we apply a texture to the mesh. Without it the mesh would be drawn using only a color.

if (node)
{
node->setMaterialFlag(EMF_LIGHTING, false);
node->setMD2Animation(scene::EMAT_STAND);
node->setMaterialTexture( 0, driver->getTexture(mediaPath + "sydney.bmp") );
}

To look at the mesh, we place a camera into 3d space at the position (0, 30, -40). The camera looks from there to (0,5,0), which is approximately the place where our md2 model is.

smgr->addCameraSceneNode(0, vector3df(0,30,-40), vector3df(0,5,0));

OK, now we have set up the scene, lets draw everything: We run the device in a while() loop, until the device does not want to run any more. This would be when the user closes the window or presses ALT+F4 (or whatever keycode closes a window on your OS).

while(device->run())
{

Anything can be drawn between a beginScene() and an endScene() call. The beginScene() call clears the screen with a color and the depth buffer, if desired. Then we let the Scene Manager and the GUI Environment draw their content. With the endScene() call everything is presented on the screen.

driver->beginScene(ECBF_COLOR | ECBF_DEPTH, SColor(255,100,101,140));
smgr->drawAll();
guienv->drawAll();
driver->endScene();
}

After we are done with the render loop, we have to delete the Irrlicht Device created before with createDevice(). In the Irrlicht Engine, you have to delete all objects you created with a method or function which starts with 'create'. The object is simply deleted by calling ->drop(). See the documentation at irr::IReferenceCounted::drop() for more information.

device->drop();
return 0;
}

That's it. Compile and run.

+
+ + +

 

+ + diff --git a/examples/02.Quake3Map/tutorial.html b/examples/02.Quake3Map/tutorial.html index 1858b866..ddc6a5bf 100644 --- a/examples/02.Quake3Map/tutorial.html +++ b/examples/02.Quake3Map/tutorial.html @@ -1,181 +1,208 @@ - - -Irrlicht Engine Tutorial - - - - -
- - - - - - - - -
- Tutorial 2.Quake3Map
-

This Tutorial shows how to load a Quake 3 map into the engine, create - a SceneNode for optimizing the speed of rendering and how to create - a user controlled camera. Please note that you should know the basics - of the engine before starting this tutorial, just take a short look - at the first tutorial, 1.HelloWorld, if you haven't done this yet.
- The result of this example will look like this:

-


-

-
-
- - - - - - - -
- Lets start!
-

Lets start like the HelloWorld example: We include the irrlicht header - files and an additional file to be able
- to ask the user for a driver type using the console.

- - - - -
#include <irrlicht.h>
#include <iostream>
-

As already written in the HelloWorld example, in the Irrlicht Engine, - everything can be found in the namespace 'irr'. To get rid of the irr:: - in front of the name of every class, we tell the compiler that we use - that namespace from now on, and we will not have to write that 'irr::'.
- There are 5 other sub namespaces 'core', 'scene', 'video', 'io' and - 'gui'. Unlike in the HelloWorld example, we do not a 'using namespace' - for these 5 other namespaces because in this way you will see what can - be found in which namespace. But if you like, you can also include the - namespaces like in the previous example. Code just like you want to.

- - - - -
using namespace irr;
-

Again, to be able to use the Irrlicht.DLL file, we need to link with - the Irrlicht.lib. We could set this option in the project settings, - but to make it easy, we use a pragma comment lib:

- - - - -
#pragma comment(lib, "Irrlicht.lib")
- -
-

Ok, lets start. Again, we use the main() method as start, not the WinMain(), - because its shorter to write.

- - - - -
int main()
{
-

Like in the HelloWorld example, we create an IrrlichtDevice with createDevice(). - The difference now is that we ask the user to select which hardware accelerated - driver to use. The Software device would be too slow to draw a huge Quake - 3 map, but just for the fun of it, we make this decision possible too.

- - - - -
// ask user for driver

video::E_DRIVER_TYPE driverType = video::EDT_DIRECT3D9;

printf("Please select the driver you want for this example:\n"\
" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
" (d) Software Renderer\n (e) Apfelbaum Software Renderer\n"\
" (f) NullDevice\n (otherKey) exit\n\n");
-char i;
std::cin >> i;

switch(i)
{
case 'a': driverType = video::EDT_DIRECT3D9;break;
case 'b': driverType = video::EDT_DIRECT3D8;break;
case 'c': driverType = video::EDT_OPENGL; break;
case 'd': driverType = video::EDT_SOFTWARE; break;
case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
case 'f': driverType = video::EDT_NULL; break;
default: return 1;
}

// create device and exit if creation failed

IrrlichtDevice *device =
createDevice(driverType, core::dimension2d<s32>(640, 480));

if (device == 0)
return 1;
-

Get a pointer to the video driver and the SceneManager so that we do - not always have to write device->getVideoDriver() and device->getSceneManager().

- - - - -
video::IVideoDriver* driver = device->getVideoDriver();
-scene::ISceneManager* smgr = device->getSceneManager();
-

To display the Quake 3 map, we first need to load it. Quake 3 maps are - packed into .pk3 files wich are nothing other than .zip files. So we add - the .pk3 file to our FileSystem. After it was added, we are able to read - from the files in that archive as they would directly be stored on disk.

- - - - -
device->getFileSystem()->addZipFileArchive("../../media/map-20kdm2.pk3");
-

Now we can load the mesh by calling getMesh(). We get a pointer returned - to a IAnimatedMesh. As you know, Quake 3 maps are not really animated, - they are only a huge chunk of static geometry with some materials attached. - Hence the IAnimated mesh consists of only one frame,
- so we get the "first frame" of the "animation", which - is our quake level and create an OctTree scene node with it, using addOctTreeSceneNode(). - The OctTree optimizes the scene a little bit, trying to draw only geometry - which is currently visible. An alternative to the OctTree would be a AnimatedMeshSceneNode, - which would draw always the complete geometry of the mesh, without optimization. - Try it out: Write addAnimatedMeshSceneNode instead of addOctTreeSceneNode - and compare the primitives drawed by the video driver. (There is a getPrimitiveCountDrawed() - method in the IVideoDriver class). Note that this optimization with the - Octree is only useful when drawing huge meshes consiting of lots of geometry.

- - - - -
scene::IAnimatedMesh* mesh = smgr->getMesh("20kdm2.bsp");
scene::ISceneNode* node = 0; - -if (mesh)
node = smgr->addOctTreeSceneNode(mesh->getMesh(0));
-

Because the level was modelled not around the origin (0,0,0), we translate - the whole level a little bit.

- - - - -
if (node)
node->setPosition(core::vector3df(-1300,-144,-1249));
-

Now we only need a Camera to look at the Quake 3 map. And we want to - create a user controlled camera. There are some different cameras available - in the Irrlicht engine. For example the Maya Camera which can be controlled - compareable to the camera in Maya: Rotate with left mouse button pressed, - Zoom with both buttons pressed,
- translate with right mouse button pressed. This could be created with - addCameraSceneNodeMaya(). But for this example, we want to create a camera - which behaves like the ones in first person shooter games (FPS):

- - - - -
smgr->addCameraSceneNodeFPS();
-

The mouse cursor needs not to be visible, so we make it invisible.

- - - - -
device->getCursorControl()->setVisible(false);
-

We have done everything, so lets draw it. We also write the current frames - per second and the drawn primitives to the caption of the window. The - 'if (device->isWindowActive())' line is optional, but prevents the - engine render to set the position of the mouse cursor after task switching - when other program are active.

- - - - -
int lastFPS = -1;
-
while(device->run())
-{
-  driver->beginScene(true, true, video::SColor(0,200,200,200));
-  smgr->drawAll();
-  driver->endScene();
-
  int fps = driver->getFPS();
-
  if (lastFPS != fps)
-  {
-     core::stringw str = L"Irrlicht Engine - Quake 3 Map example [";
str += driver->getName();
str += "] FPS:";
str += fps;
device->setWindowCaption(str.c_str());
lastFPS = fps; - } -}
-

In the end, delete the Irrlicht device.

- - - - -
  device->drop();
return 0;
}
-

That's it. Compile and play around with the program.

-

 

-

 

- - + + + + + + + +Tutorial 2: Quake3Map + + + + +
+ + + +
+
+
+
Tutorial 2: Quake3Map
+
+
+
+002shot.jpg +
+

This tutorial shows how to load a Quake 3 map into the engine, create a SceneNode for optimizing the speed of rendering, and how to create a user controlled camera.

+

Please note that you should know the basics of the engine before starting this tutorial. Just take a short look at the first tutorial, if you haven't done this yet: http://irrlicht.sourceforge.net/docu/example001.html

+

Lets start like the HelloWorld example: We include the irrlicht header files and an additional file to be able to ask the user for a driver type using the console.

#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"

As already written in the HelloWorld example, in the Irrlicht Engine everything can be found in the namespace 'irr'. To get rid of the irr:: in front of the name of every class, we tell the compiler that we use that namespace from now on, and we will not have to write that 'irr::'. There are 5 other sub namespaces 'core', 'scene', 'video', 'io' and 'gui'. Unlike in the HelloWorld example, we do not call 'using namespace' for these 5 other namespaces, because in this way you will see what can be found in which namespace. But if you like, you can also include the namespaces like in the previous example.

using namespace irr;

Again, to be able to use the Irrlicht.DLL file, we need to link with the Irrlicht.lib. We could set this option in the project settings, but to make it easy, we use a pragma comment lib:

#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

OK, lets start. Again, we use the main() method as start, not the WinMain().

int main()
{

Like in the HelloWorld example, we create an IrrlichtDevice with createDevice(). The difference now is that we ask the user to select which video driver to use. The Software device might be too slow to draw a huge Quake 3 map, but just for the fun of it, we make this decision possible, too.

// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole(true);
if (driverType==video::EDT_COUNT)
return 1;
// create device and exit if creation failed
IrrlichtDevice *device =
createDevice(driverType, core::dimension2d<u32>(640, 480));
if (device == 0)
return 1; // could not create selected driver.

Get a pointer to the video driver and the SceneManager so that we do not always have to call irr::IrrlichtDevice::getVideoDriver() and irr::IrrlichtDevice::getSceneManager().

video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();

To display the Quake 3 map, we first need to load it. Quake 3 maps are packed into .pk3 files which are nothing else than .zip files. So we add the .pk3 file to our irr::io::IFileSystem. After it was added, we can read from the files in that archive as if they were stored on disk.

device->getFileSystem()->addFileArchive(getExampleMediaPath() + "map-20kdm2.pk3");

Now we can load the mesh by calling irr::scene::ISceneManager::getMesh(). We get a pointer returned to an irr::scene::IAnimatedMesh. Quake 3 maps are not really animated, they are only a chunk of static geometry with some materials attached. Hence the IAnimatedMesh consists of only one frame, so we get the "first frame" of the "animation", which is our quake level and create an Octree scene node with it, using irr::scene::ISceneManager::addOctreeSceneNode(). The Octree optimizes the scene a little bit, trying to draw only geometry which is currently visible. An alternative to the Octree would be a irr::scene::IMeshSceneNode, which would always draw the complete geometry of the mesh, without optimization. Try it: Use irr::scene::ISceneManager::addMeshSceneNode() instead of addOctreeSceneNode() and compare the primitives drawn by the video driver. (There is a irr::video::IVideoDriver::getPrimitiveCountDrawn() method in the irr::video::IVideoDriver class). Note that this optimization with the Octree is only useful when drawing huge meshes consisting of lots of geometry and if users can't see the whole scene at once.

scene::IAnimatedMesh* mesh = smgr->getMesh("20kdm2.bsp");
scene::ISceneNode* node = 0;
if (mesh)
node = smgr->addOctreeSceneNode(mesh->getMesh(0), 0, -1, 1024);
// node = smgr->addMeshSceneNode(mesh->getMesh(0));

Because the level was not modeled around the origin (0,0,0), we translate the whole level a little bit. This is done on irr::scene::ISceneNode level using the methods irr::scene::ISceneNode::setPosition() (in this case), irr::scene::ISceneNode::setRotation(), and irr::scene::ISceneNode::setScale().

if (node)
node->setPosition(core::vector3df(-1300,-144,-1249));

Now we need a camera to look at the Quake 3 map. We want to create a user controlled camera. There are some cameras available in the Irrlicht engine. For example the MayaCamera which can be controlled like the camera in Maya: Rotate with left mouse button pressed, Zoom with both buttons pressed, translate with right mouse button pressed. This could be created with irr::scene::ISceneManager::addCameraSceneNodeMaya(). But for this example, we want to create a camera which behaves like the ones in first person shooter games (FPS) and hence use irr::scene::ISceneManager::addCameraSceneNodeFPS().

smgr->addCameraSceneNodeFPS();

The mouse cursor needs not be visible, so we hide it via the irr::IrrlichtDevice::ICursorControl.

device->getCursorControl()->setVisible(false);

Everything is set up, so lets draw it. We also write the current frames per second and the primitives drawn into the caption of the window. The test for irr::IrrlichtDevice::isWindowActive() is optional, but prevents the engine to grab the mouse cursor after task switching when other programs are active. The call to irr::IrrlichtDevice::yield() will avoid the busy loop to eat up all CPU cycles when the window is not active.

int lastFPS = -1;
while(device->run())
{
if (device->isWindowActive())
{
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(255,200,200,200));
smgr->drawAll();
driver->endScene();
int fps = driver->getFPS();
if (lastFPS != fps)
{
core::stringw str = L"Irrlicht Engine - Quake 3 Map example [";
str += driver->getName();
str += "] FPS:";
str += fps;
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}
else
device->yield();
}

In the end, delete the Irrlicht device.

device->drop();
return 0;
}

That's it. Compile and play around with the program.

+
+ + +

 

+ + diff --git a/examples/03.CustomSceneNode/tutorial.html b/examples/03.CustomSceneNode/tutorial.html index becc3d77..bfa3a7b9 100644 --- a/examples/03.CustomSceneNode/tutorial.html +++ b/examples/03.CustomSceneNode/tutorial.html @@ -1,222 +1,209 @@ - - -Irrlicht Engine Tutorial - - - - -
- - - - - - - - -
-
-
-
Tutorial 3.CustomSceneNode
-
-
-
-

This Tutorial is a tutorial for more advanced developers. If you are - currently just playing around with the Irrlicht engine, please look - at other examples first. This tutorial shows how to create a custom - scene node and how to use it in the engine. A custom scene node is needed, - if you want to implement a render technique, the Irrlicht Engine is - currently not supporting. For example you can write a indoor portal - based renderer or a advanced terrain scene node with it. With creating - custom scene nodes, you can easily extend the Irrlicht Engine and adapt - it to your needs.

-

I will keep the tutorial simple: Keep everything very short, everything - in one .cpp file, and I'll use the engine here as in all other tutorials. - At the end of the tutorial, the result will look like the image below. - This looks not very exciting, but it is a complete customized scene - node and a good point to start from creating you own scene nodes.

-


-

-
-
-
- - - - - - - -
- Lets start!
-

To start, I include the header files, use the irr namespace, and tell - the linker to link with the .lib file.

- - - - -
#include <irrlicht.h>
using namespace irr;
#pragma comment(lib, "Irrlicht.lib")
-

Here comes the most sophisticated part of this tutorial: The class - of our very own custom scene node. To keep it simple,
- our scene node will not be an indoor portal renderer nor a terrain scene - node, but a simple tetraeder, a 3d object consiting of 4 connected vertices, - which only draws itself and does nothing more.

-

To let our scene node be able to be inserted into the Irrlicht Engine - scene, the class we create needs only be derived from the ISceneNode - class and has to override some methods.

- - - - -
class CSampleSceneNode : public scene::ISceneNode
{
-

First, we declare some member variables, to hold data for our tetraeder: - The bounding box, 4 vertices, and
- the material of the tetraeder.

- - - - -
core::aabbox3d<f32> Box;
video::S3DVertex Vertices[4];
video::SMaterial Material;
-

The parameters of the constructor specify the parent of the scene node, - a pointer to the scene manager, and an id of the scene node. In the - constructor itself, we call the parent classes constructor, set some - properties of the material we use to draw the scene node and create - the 4 vertices of the tetraeder we will draw later.

- - - - -
public:
CSampleSceneNode(scene::ISceneNode* parent, scene::ISceneManager* mgr, s32 id)
- : scene::ISceneNode(parent, mgr, id)
-{ 
-  Material.Wireframe = false;
-  Material.Lighting = false;
-
  Vertices[0] = video::S3DVertex(0,0,10, 1,1,0,video::SColor(255,0,255,255),0,1);
-  Vertices[1] = video::S3DVertex(10,0,-10, 1,0,0,video::SColor(255,255,0,255),1,1); 
-  Vertices[2] = video::S3DVertex(0,20,0, 0,1,1,video::SColor(255,255,255,0),1,0);
-  Vertices[3] = video::S3DVertex(-10,0,-10, 0,0,1,video::SColor(255,0,255,0),0,0);
-
-
- The Irrlicht Engine needs to know the bounding box of your scene node. - It will use it for doing automatic culling and other things. Hence we - need to create a bounding box from the 4 vertices we use. If you do not - want the engine to use the box for automatic culling, and/or don't want - to create the box, you could also write
- AutomaticCullingEnabled = false;.
-
- - - - -
  Box.reset(Vertices[0].Pos);
for (s32 i=1; i<4; ++i)
Box.addInternalPoint(Vertices[i].Pos); -}
-
-

Before it is drawn, the OnPreRender() method of every scene node in - the scene is called by the scene manager. If the scene node wishes to - draw itself, it may register itself in the scene manager to be drawn. - This is necessary to tell the scene manager when it should call the - ::render method. For example normal scene nodes render their content - one after another, while stencil buffer shadows would like to be drawn - after all other scene nodes. And camera or light scene nodes need to - be rendered before all other scene nodes (if at all).
- So here we simply register the scene node to get rendered normally. - If we would like to let it be rendered like cameras or light, we would - have to call SceneManager->registerNodeForRendering(this, SNRT_LIGHT_AND_CAMERA); -
- After this, we call the OnPreRender-method of the base class ISceneNode, - which simply lets also all the child scene nodes of this node register - themselves.

-
- - - - -
virtual void OnPreRender()
{
if (IsVisible)
SceneManager->registerNodeForRendering(this); - - ISceneNode::OnPreRender(); -}
-

In the render() method most of the interresting stuff happenes: The Scene - node renders itself. We override this method and draw the tetraeder.

- - - - -
virtual void render()
{
u16 indices[] = { 0,2,3, 2,1,3, 1,0,3, 2,0,1 }; - video::IVideoDriver* driver = SceneManager->getVideoDriver();
-
  driver->setMaterial(Material);
-  driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
-  driver->drawIndexedTriangleList(&Vertices[0], 4, &indices[0], 4);
-}
-

At least, we create three small additional methods. GetBoundingBox() - returns the bounding box of this scene node,
- GetMaterialCount() returns the amount of materials in this scene node - (our tetraeder only has one material), and getMaterial() returns the material - at an index. Because we have only one material here, we can return the - only one material, assuming that no one ever calls getMaterial() with - an index greater than 0.

- - - - -
  virtual const core::aabbox3d<f32>& getBoundingBox() const
{
return Box;
}
  virtual u32 getMaterialCount()
-  {
-    return 1;
-  }
  virtual video::SMaterial& getMaterial(u32 i)
-  {
-    return Material;
-  } 
-};
-

That's it. The Scene node is done. Now we simply have to start the engine, - create the scene node and a camera, and look at the result.

- - - - -
int main()
{ - IrrlichtDevice *device = - createDevice(video::EDT_OPENGL, core::dimension2d<s32>(640, 480), 16, false);
  device->setWindowCaption(L"Custom Scene Node - Irrlicht Engine Demo");
  video::IVideoDriver* driver = device->getVideoDriver();
-           scene::ISceneManager* smgr = device->getSceneManager();
-
  smgr->addCameraSceneNode(0, core::vector3df(0,-40,0), core::vector3df(0,0,0));
-                   
-

Create our scene node. Note that it is dropped (->drop()) instantly - after we create it. This is possible because the scene manager now takes - care of it. This is not nessecary, it would also be possible to drop it - at the end of the program.

- - - - -
CSampleSceneNode *myNode = 
new CSampleSceneNode(smgr->getRootSceneNode(), smgr, 666); - -myNode->drop();
-

To animate something in this boring scene consisting only of one tetraeder, - and to show, that you now can use your scene node like any other scene - node in the engine, we add an animator to the scene node, which rotates - the node a little bit.

- - - - -
scene::ISceneNodeAnimator* anim = 
smgr->createRotationAnimator(core::vector3df(0.8f, 0, 0.8f)); - -myNode->addAnimator(anim); -anim->drop();
-

Now draw everything and finish.

- - - - -
  while(device->run())
{
driver->beginScene(true, true, video::SColor(0,100,100,100)); - - smgr->drawAll(); - - driver->endScene(); - } - -device->drop(); -return 0; -}
-

That's it. Compile and play around with the program.

-

 

- - + + + + + + + +Tutorial 3: Custom SceneNode + + + + +
+ + + +
+
+
+
Tutorial 3: Custom SceneNode
+
+
+
+003shot.jpg +
+

This tutorial is more advanced than the previous ones. If you are currently just playing around with the Irrlicht engine, you may want to look at other examples first. This tutorials shows how to create a custom scene node and how to use it in the engine. A custom scene node is needed if you want to implement a render technique the Irrlicht Engine currently does not support. For example, you can write an indoor portal based renderer or an advanced terrain scene node with it. By creating custom scene nodes, you can easily extend the Irrlicht Engine and adapt it to your needs.

+

I will keep the tutorial simple: Keep everything very short and everything in one .cpp file. This is the style which will also be used in most of the following tutorials.

+

To start, I include the header files, use the irr namespace, and tell the linker to link with the .lib file.

#include <irrlicht.h>
#include "driverChoice.h"
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

Here comes the more sophisticated part of this tutorial: The class of our very own custom scene node. To keep it simple, our scene node will not be an indoor portal renderer nor a terrain scene node, but a simple tetrahedron, a 3D object consisting of 4 connected vertices, which only draws itself and does nothing more. Note that this scenario does not require a custom scene node in Irrlicht. Instead one would create a mesh from the geometry and pass it to a irr::scene::IMeshSceneNode. This example just illustrates creation of a custom scene node in a simple setting.

+

To allow our scene node to be inserted into the Irrlicht Engine scene, the class we create needs to be derived from the irr::scene::ISceneNode class and has to override some methods.

class CSampleSceneNode : public scene::ISceneNode
{

First, we declare some member variables: The bounding box, 4 vertices, and the material of the tetrahedron.

core::aabbox3d<f32> Box;
video::S3DVertex Vertices[4];
video::SMaterial Material;
public:

The parameters of the constructor specify the parent of the scene node, a pointer to the scene manager, and an id of the scene node. In the constructor we call the parent class' constructor, set some properties of the material, and create the 4 vertices of the tetrahedron.

CSampleSceneNode(scene::ISceneNode* parent, scene::ISceneManager* mgr, s32 id)
: scene::ISceneNode(parent, mgr, id)
{
Material.Wireframe = false;
Material.Lighting = false;
Vertices[0] = video::S3DVertex(0,0,10, 1,1,0,
video::SColor(255,0,255,255), 0, 1);
Vertices[1] = video::S3DVertex(10,0,-10, 1,0,0,
video::SColor(255,255,0,255), 1, 1);
Vertices[2] = video::S3DVertex(0,20,0, 0,1,1,
video::SColor(255,255,255,0), 1, 0);
Vertices[3] = video::S3DVertex(-10,0,-10, 0,0,1,
video::SColor(255,0,255,0), 0, 0);

The Irrlicht Engine needs to know the bounding box of a scene node. It will use it for automatic culling and other things. Hence, we need to create a bounding box from the 4 vertices we use. If you do not want the engine to use the box for automatic culling, and/or don't want to create the box, you could also call irr::scene::ISceneNode::setAutomaticCulling() with irr::scene::EAC_OFF.

Box.reset(Vertices[0].Pos);
for (s32 i=1; i<4; ++i)
Box.addInternalPoint(Vertices[i].Pos);
}

Before it is drawn, the irr::scene::ISceneNode::OnRegisterSceneNode() method of every scene node in the scene is called by the scene manager. If the scene node wishes to draw itself, it may register itself in the scene manager to be drawn. This is necessary to tell the scene manager when it should call irr::scene::ISceneNode::render(). For example, normal scene nodes render their content one after another, while stencil buffer shadows would like to be drawn after all other scene nodes. And camera or light scene nodes need to be rendered before all other scene nodes (if at all). So here we simply register the scene node to render normally. If we would like to let it be rendered like cameras or light, we would have to call SceneManager->registerNodeForRendering(this, SNRT_LIGHT_AND_CAMERA); After this, we call the actual irr::scene::ISceneNode::OnRegisterSceneNode() method of the base class, which lets all the child scene nodes of this node register themselves.

virtual void OnRegisterSceneNode()
{
if (IsVisible)
SceneManager->registerNodeForRendering(this);
ISceneNode::OnRegisterSceneNode();
}

In the render() method most of the interesting stuff happens: The Scene node renders itself. We override this method and draw the tetrahedron.

virtual void render()
{

Indices into the 'Vertices' array. A triangle needs 3 vertices so you have to pass the 3 corresponding indices for each triangle to tell which of the vertices should be used for it.

u16 indices[] = { 0,2,3, 2,1,3, 1,0,3, 2,0,1 };
video::IVideoDriver* driver = SceneManager->getVideoDriver();
driver->setMaterial(Material);
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
driver->drawVertexPrimitiveList(&Vertices[0], 4, &indices[0], 4, video::EVT_STANDARD, scene::EPT_TRIANGLES, video::EIT_16BIT);
}

And finally we create three small additional methods. irr::scene::ISceneNode::getBoundingBox() returns the bounding box of this scene node, irr::scene::ISceneNode::getMaterialCount() returns the amount of materials in this scene node (our tetrahedron only has one material), and irr::scene::ISceneNode::getMaterial() returns the material at an index. Because we have only one material, we can return that and assume that no one ever calls getMaterial() with an index greater than 0.

virtual const core::aabbox3d<f32>& getBoundingBox() const
{
return Box;
}
virtual u32 getMaterialCount() const
{
return 1;
}
virtual video::SMaterial& getMaterial(u32 i)
{
return Material;
}
};

That's it. The Scene node is done. Now we start the engine, create the scene node and a camera, and look at the result.

int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// create device
IrrlichtDevice *device = createDevice(driverType,
core::dimension2d<u32>(640, 480), 16, false);
if (device == 0)
return 1; // could not create selected driver.
// set window caption, get some pointers, create a camera
device->setWindowCaption(L"Custom Scene Node - Irrlicht Engine Demo");
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
smgr->addCameraSceneNode(0, core::vector3df(0,-40,0), core::vector3df(0,0,0));

Create our scene node. I don't check the result of calling new, as it should throw an exception rather than returning 0 on failure. Because the new node will create itself with a reference count of 1, and then will have another reference added by its parent scene node when it is added to the scene, I need to drop my reference to it. Best practice is to drop it only after I have finished using it, regardless of what the reference count of the object is after creation.

CSampleSceneNode *myNode =
new CSampleSceneNode(smgr->getRootSceneNode(), smgr, 666);

To animate something in this boring scene consisting only of one tetrahedron, and to show that you now can use your scene node like any other scene node in the engine, we add an animator to the scene node, which rotates the node a little bit. irr::scene::ISceneManager::createRotationAnimator() could return 0, so should be checked.

scene::ISceneNodeAnimator* anim =
smgr->createRotationAnimator(core::vector3df(0.8f, 0, 0.8f));
if(anim)
{
myNode->addAnimator(anim);

I'm done referring to anim, so must irr::IReferenceCounted::drop() this reference now because it was produced by a createFoo() function. As I shouldn't refer to it again, ensure that I can't by setting to 0.

anim->drop();
anim = 0;
}

I'm done with my CSampleSceneNode object, and so must drop my reference. This won't delete the object, yet, because it is still attached to the scene graph, which prevents the deletion until the graph is deleted or the custom scene node is removed from it.

myNode->drop();
myNode = 0; // As I shouldn't refer to it again, ensure that I can't

Now draw everything and finish.

u32 frames=0;
while(device->run())
{
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(0,100,100,100));
smgr->drawAll();
driver->endScene();
if (++frames==100) // don't update more often, setWindowCaption can be expensive
{
core::stringw str = L"Irrlicht Engine [";
str += driver->getName();
str += L"] FPS: ";
str += (s32)driver->getFPS();
device->setWindowCaption(str.c_str());
frames=0;
}
}
device->drop();
return 0;
}

That's it. Compile and play around with the program.

+
+ + +

 

+ + diff --git a/examples/04.Movement/tutorial.html b/examples/04.Movement/tutorial.html index 28b207dc..cb793b21 100644 --- a/examples/04.Movement/tutorial.html +++ b/examples/04.Movement/tutorial.html @@ -1,188 +1,208 @@ - - -Irrlicht Engine Tutorial - - - - -
- - - - - - - - -
-
-
-
Tutorial 4.Movement
-
-
-
-

This Tutorial shows how to move and animate SceneNodes. The basic concept - of SceneNodeAnimators is shown as well as manual movement of nodes using - the keyboard.

-

The program which is described here will look like this:

-


-

-
-
-
- - - - - - - -
- Lets start!
-

As always, I include the header files, use the irr namespace, and tell - the linker to link with the .lib file.

- - - - -
#include <stdio.h>
#include <wchar.h>
#include <irrlicht.h>
-
using namespace irr;
-
#pragma comment(lib, "Irrlicht.lib")
-

In this tutorial, one of our goals is to move a scene node using some - keys on the keyboard. We store a pointer to the scene node we want to - move with the keys here.
- The other pointer is a pointer to the Irrlicht Device, which we need - int the EventReceiver to manipulate the scene node and to get the active - camera.

- - - - -
scene::ISceneNode* node = 0;
IrrlichtDevice* device = 0;
-

To get events like mouse and keyboard input, or GUI events like "the - OK button has been clicked", we need an object wich is derived - from the IEventReceiver object. There is only one method to override: - OnEvent. This method will be called by the engine when an event happened. - We will use this input to move the scene node with the keys W and S.

- - - - -
class MyEventReceiver : public IEventReceiver
{
public:
virtual bool OnEvent(const SEvent& event)
{
-

If the key 'W' or 'S' was left up, we get the position of the scene - node, and modify the Y coordinate a little bit. So if you press 'W', - the node moves up, and if you press 'S' it moves down.

- - - - -
if (node != 0 && event.EventType == irr::EET_KEY_INPUT_EVENT&&
!event.KeyInput.PressedDown)
{
switch(event.KeyInput.Key)
{
case KEY_KEY_W:
case KEY_KEY_S:
{
core::vector3df v = node->getPosition();
v.Y += event.KeyInput.Key == KEY_KEY_W ? 2.0f : -2.0f;
node->setPosition(v);
}
return true;
}
} return false;
}
};
- -
-

The event receiver for moving a scene node is ready. So lets just create - an Irrlicht Device and the scene node we want to move. We also create - some other additional scene nodes, to show that there are also some different - possibilities to move and animate scene nodes.

- - - - -
int main()
{
MyEventReceiver receiver; - - device = createDevice(video::EDT_OPENGL, core::dimension2d<s32>(640, 480), - 16, false, false, false, &receiver);
-
   video::IVideoDriver* driver = device->getVideoDriver();
-   scene::ISceneManager* smgr = device->getSceneManager();
-
-

Create the node for moving it with the 'W' and 'S' key. We create a - sphere node, which is a built in geometric primitive scene node. - We place the node at (0,0,30) and assign a texture to it to let it look - a little bit more interesting.

- - - - -
node = smgr->addSphereSceneNode();
-node->setPosition(core::vector3df(0,0,30));
-node->setMaterialFlag(video::EMF_LIGHTING, false);
-node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
-

Now we create another node, moving using a scene node animator. Scene - node animators modify scene nodes and can be attached to any scene node - like
- mesh scene nodes, billboards, lights and even camera scene nodes. Scene - node animators are not only able to modify the position of a scene node, - they can
- also animate the textures of an object for example. We create a test scene - node again an attach a 'fly circle' scene node to it, letting this node - fly around our first test scene node.

- - - - -
scene::ISceneNode* n = smgr->addCubeSceneNode();
-n->setMaterialTexture(0, driver->getTexture("../../media/t351sml.jpg"));
-n->setMaterialFlag(video::EMF_LIGHTING, false);
-scene::ISceneNodeAnimator* anim = 
-	smgr->createFlyCircleAnimator(core::vector3df(0,0,30), 20.0f);
-n->addAnimator(anim);
-anim->drop();
-

The last scene node we add to show possibilities of scene node animators - is a md2 model, which uses a 'fly straight' animator to run between to - points.

- - - - -
scene::IAnimatedMeshSceneNode* anms = smgr->addAnimatedMeshSceneNode(
smgr->getMesh("../../media/sydney.md2")); - -if (n)
{
anim = smgr->createFlyStraightAnimator(core::vector3df(100,0,60),
core::vector3df(-100,0,60), 10000, true);
anms->addAnimator(anim);
anim->drop();
-
-

To make to model look right we set the frames between which - the animation should loop, rotate the model around 180 degrees, and adjust - the animation speed and the texture.
- To set the right animation (frames and speed), we would also be able to - just call "anms->setMD2Animation(scene::EMAT_RUN)" for the - 'run' animation instead of "setFrameLoop" and "setAnimationSpeed", - but this only works with MD2 animations, and so you know how to start - other animations.

- - - - -
   anms->setMaterialFlag(video::EMF_LIGHTING, false);
anms->setFrameLoop(320, 360); - anms->setAnimationSpeed(30);
anms->setRotation(core::vector3df(0,180.0f,0));
anms->setMaterialTexture(0, driver->getTexture("../../media/sydney.bmp"));
}
-

To be able to look at and move around in this scene, we create a first - person shooter style camera and make the mouse cursor invisible.

- - - - -
smgr->addCameraSceneNodeFPS(0, 100.0f, 100.0f);
device->getCursorControl()->setVisible(false);
-

We have done everything, so lets draw it. We also write the current frames - per second and the name of the driver to the caption of the window.

- - - - -
int lastFPS = -1;
-
while(device->run())
-{
-     driver->beginScene(true, true, video::SColor(255,90,90,156));
-     smgr->drawAll();
-     driver->endScene();
-
     int fps = driver->getFPS();
-
     if (lastFPS != fps)
-     {
-        wchar_t tmp[1024];
-        swprintf(tmp, 1024, L"Movement Example - Irrlicht Engine (%ls)(fps:%d)",
driver->getName(), fps);
-
       device->setWindowCaption(tmp);
-       lastFPS = fps;
-     }
-}
-
-device->drop();
return 0;
}
-

That's it. Compile and play around with the program.

-

 

-

 

- - + + + + + + + +Tutorial 4: Movement + + + + +
+ + + +
+
+
+
Tutorial 4: Movement
+
+
+
+004shot.jpg +
+

This tutorial shows how to move and animate SceneNodes. The basic concept of SceneNodeAnimators is shown as well as manual movement of nodes using the keyboard. We'll demonstrate framerate independent movement, which means moving by an amount dependent on the duration of the last run of the Irrlicht loop.

+

Example 19.MouseAndJoystick shows how to handle other input than keyboard.

+

As always, include the header files, use the irr namespace, and tell the linker to link with the .lib file.

#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
using namespace irr;

To receive events like mouse and keyboard input, or GUI events like "button has been clicked", we need an object which is derived from the irr::IEventReceiver object. There is only one method to override: irr::IEventReceiver::OnEvent(). This method will be called by the engine once when an event happens. What we really want to know is whether a key is being held down, and so we will remember the current state of each key.

class MyEventReceiver : public IEventReceiver
{
public:
// This is the one method that we have to implement
virtual bool OnEvent(const SEvent& event)
{
// Remember whether each key is down or up
if (event.EventType == irr::EET_KEY_INPUT_EVENT)
KeyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;

Always return false by default. If you return true you tell the engine that you handled this event completely and the Irrlicht should not process it any further. So for example if you return true for all EET_KEY_INPUT_EVENT events then Irrlicht would not pass on key-events to it's GUI system.

return false;
}
// This is used to check whether a key is being held down
virtual bool IsKeyDown(EKEY_CODE keyCode) const
{
return KeyIsDown[keyCode];
}
MyEventReceiver()
{
for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
KeyIsDown[i] = false;
}
private:
// We use this array to store the current state of each key
bool KeyIsDown[KEY_KEY_CODES_COUNT];
};

The event receiver for keeping the pressed keys is ready, the actual responses will be made inside the render loop, right before drawing the scene. So lets create an irr::IrrlichtDevice and the scene node we want to move. We also create some additional scene nodes to show different possibilities to move and animate scene nodes.

int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;

Create the event receiver. Take care that the pointer to it has to stay valid as long as the IrrlichtDevice uses it. Event receivers are not reference counted.

MyEventReceiver receiver;
// create device
IrrlichtDevice* device = createDevice(driverType,
core::dimension2d<u32>(640, 480), 16, false, false, false, &receiver);
if (device == 0)
return 1; // could not create selected driver.
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
const io::path mediaPath = getExampleMediaPath();

Create the node which will be moved with the WSAD keys. We create a sphere node, which is a built-in geometry primitive. We place the node at (0,0,30) and assign a texture to it to let it look a little bit more interesting. Because we have no dynamic lights in this scene we disable lighting for each model (otherwise the models would be black).

scene::ISceneNode * sphereNode = smgr->addSphereSceneNode();
if (sphereNode)
{
sphereNode->setPosition(core::vector3df(0,0,30));
sphereNode->setMaterialTexture(0, driver->getTexture(mediaPath + "wall.bmp"));
sphereNode->setMaterialFlag(video::EMF_LIGHTING, false);
}

Now we create another node, movable using a scene node animator. Scene node animators modify scene nodes and can be attached to any scene node like mesh scene nodes, billboards, lights and even camera scene nodes. Scene node animators are not only able to modify the position of a scene node, they can also animate the textures of an object for example. We create a cube scene node and attach a 'fly circle' scene node animator to it, letting this node fly around our sphere scene node.

scene::ISceneNode* cubeNode = smgr->addCubeSceneNode();
if (cubeNode)
{
cubeNode->setMaterialTexture(0, driver->getTexture(mediaPath + "t351sml.jpg"));
cubeNode->setMaterialFlag(video::EMF_LIGHTING, false);
scene::ISceneNodeAnimator* anim =
smgr->createFlyCircleAnimator(core::vector3df(0,0,30), 20.0f);
if (anim)
{
cubeNode->addAnimator(anim);
anim->drop();
}
}

The last scene node we add is a b3d model of a walking ninja. Is shows the use of a 'fly straight' animator to move the node between two points.

scene::IAnimatedMeshSceneNode* ninjaNode =
smgr->addAnimatedMeshSceneNode(smgr->getMesh(mediaPath + "ninja.b3d"));
if (ninjaNode)
{
scene::ISceneNodeAnimator* anim =
smgr->createFlyStraightAnimator(core::vector3df(100,0,60),
core::vector3df(-100,0,60), 3500, true);
if (anim)
{
ninjaNode->addAnimator(anim);
anim->drop();
}

To make the model look right we disable lighting, set the frames between which the animation should loop, rotate the model around 180 degrees, and adjust the animation speed and the texture. To set the correct animation (frames and speed), we would also be able to just call "ninjaNode->setMD2Animation(scene::EMAT_RUN)" for the 'run' animation instead of "setFrameLoop" and "setAnimationSpeed", But that only works with MD2 animations, while this can be used to start other animations. For MD2 it's usually good advice not to use hardcoded frame-numbers...

ninjaNode->setMaterialFlag(video::EMF_LIGHTING, false);
ninjaNode->setFrameLoop(0, 13);
ninjaNode->setAnimationSpeed(15);
// ninjaNode->setMD2Animation(scene::EMAT_RUN);
ninjaNode->setScale(core::vector3df(2.f,2.f,2.f));
ninjaNode->setRotation(core::vector3df(0,-90,0));
// ninjaNode->setMaterialTexture(0, driver->getTexture(mediaPath + "sydney.bmp"));
}

To be able to look at and move around in this scene, we create a first person shooter style camera and make the mouse cursor invisible.

smgr->addCameraSceneNodeFPS();
device->getCursorControl()->setVisible(false);

Add a colorful irrlicht logo

device->getGUIEnvironment()->addImage(
driver->getTexture(mediaPath + "irrlichtlogoalpha2.tga"),
core::position2d<s32>(10,20));

Lets draw the scene and also write the current frames per second and the name of the driver to the caption of the window.

int lastFPS = -1;
// In order to do framerate independent movement, we have to know
// how long it was since the last frame
u32 then = device->getTimer()->getTime();
// This is the movement speed in units per second.
const f32 MOVEMENT_SPEED = 5.f;
while(device->run())
{
// Work out a frame delta time.
const u32 now = device->getTimer()->getTime();
const f32 frameDeltaTime = (f32)(now - then) / 1000.f; // Time in seconds
then = now;

Check if keys W, S, A or D are being held down, and move the sphere node around respectively.

core::vector3df nodePosition = sphereNode->getPosition();
if(receiver.IsKeyDown(irr::KEY_KEY_W))
nodePosition.Y += MOVEMENT_SPEED * frameDeltaTime;
else if(receiver.IsKeyDown(irr::KEY_KEY_S))
nodePosition.Y -= MOVEMENT_SPEED * frameDeltaTime;
if(receiver.IsKeyDown(irr::KEY_KEY_A))
nodePosition.X -= MOVEMENT_SPEED * frameDeltaTime;
else if(receiver.IsKeyDown(irr::KEY_KEY_D))
nodePosition.X += MOVEMENT_SPEED * frameDeltaTime;
sphereNode->setPosition(nodePosition);
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(255,113,113,133));
smgr->drawAll(); // draw the 3d scene
device->getGUIEnvironment()->drawAll(); // draw the gui environment (the logo)
driver->endScene();
int fps = driver->getFPS();
if (lastFPS != fps)
{
core::stringw tmp(L"Movement Example - Irrlicht Engine [");
tmp += driver->getName();
tmp += L"] fps: ";
tmp += fps;
device->setWindowCaption(tmp.c_str());
lastFPS = fps;
}
}

In the end, delete the Irrlicht device.

device->drop();
return 0;
}

That's it. Compile and play around with the program.

+
+ + +

 

+ + diff --git a/examples/05.UserInterface/tutorial.html b/examples/05.UserInterface/tutorial.html index 3f3614fa..5b048a17 100644 --- a/examples/05.UserInterface/tutorial.html +++ b/examples/05.UserInterface/tutorial.html @@ -1,225 +1,206 @@ - - -Irrlicht Engine Tutorial - - - - -
- - - - - - - - -
-
-
-
Tutorial 5.User Interface
-
-
-
-

This tutorial shows how to use the built in User Interface of the Irrlicht - Engine. It will give a brief overview and show how to create and use - windows, buttons, scroll bars, static texts and list boxes.

-

The program which is described here will look like this:

-


-

-
-
-
- - - - - - - -
- Lets start!
-

As always, we include the header files (conio and curses for getting - user input from the console), and use the irrlicht namespaces. We also - store a pointer to the Irrlicht device, a counter variable for changing - the creation position of a window, and a pointer to a listbox.

- - - - -
#include <irrlicht.h>
-#include <iostream>
-using namespace irr;
-
using namespace core;
-using namespace scene;
-using namespace video;
-using namespace io;
-using namespace gui;
-
#pragma comment(lib, "Irrlicht.lib")
-
IrrlichtDevice *device = 0;
-s32 cnt = 0;
-IGUIListBox* listbox = 0;
-
-

The Event Receiver is not only capable of getting keyboard and mouse - input events, but also events of the graphical user interface (gui). - There are events for almost everything: Button click, Listbox selection - change, events that say that a element was hovered and so on. To be - able to react to some of these events, we create
- an event receiver. We only react to gui events, and if it's such an - event, we get the id of the caller (the gui element which caused the - event) and get the pointer to the gui environment.

- - - - -
class MyEventReceiver : public IEventReceiver
{
public:
virtual bool OnEvent(const SEvent& event)
{
if (event.EventType == EET_GUI_EVENT)
{
s32 id = event.GUIEvent.Caller->getID();
IGUIEnvironment* env = device->getGUIEnvironment();
-
            switch(event.GUIEvent.EventType)
-            {
-
-

If a scrollbar changed its scroll position, and it is 'our' scrollbar - (the one with id 104), then we change the
- transparency of all gui elements. This is a very easy task: There is - a skin object, in which all color settings are stored. We simply go - through all colors stored in the skin and change their alpha value. -

- - - - -
case EGET_SCROLL_BAR_CHANGED:
if (id == 104)
{
s32 pos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();

for (s32 i=0; i<EGDC_COUNT ; ++i)
{
SColor col = env->getSkin()->getColor((EGUI_DEFAULT_COLOR)i);
col.setAlpha(pos);
env->getSkin()->setColor((EGUI_DEFAULT_COLOR)i, col);
}
}
break;
-

If a button was clicked, it could be one of 'our' three buttons. If - it is the first, we shut down the engine.
- If it is the second, we create a little window with some text on it. - We also add a string to the list box to log
- what happened. And if it is the third button, we create a file open - dialog, and add also this as string to the list box.
- That's all for the event receiver.

- - - - -
-
       case EGET_BUTTON_CLICKED:
-              if (id == 101)
-              {
-                 device->closeDevice();
-                 return true;
-              }
-
              if (id == 102)
-              {
-                 listbox->addItem(L"Window created");
-                 cnt += 30;
-                 if (cnt > 200) 
-                   cnt = 0;
-
                 IGUIWindow* window = env->addWindow(
-                       rect<s32>(100 + cnt, 100 + cnt, 300 + cnt, 200 + cnt), 
false, // modal? - L"Test window");
-
                 env->addStaticText(L"Please close me", 
-                       rect<s32>(35,35,140,50),
-                       true, // border?,
-                       false, // wordwrap?
-                       window);
-
-                 return true;
-              }
-
              if (id == 103)
-              {
-                 listbox->addItem(L"File open");
-                 env->addFileOpenDialog(L"Please choose a file.");
-                 return true;
-              }
-
              break;
-          }
-       }
-       return false;
-    }
- };
-
-

Ok, now for the more interesting part. First, create the Irrlicht device. - As in some examples before, we ask the user which driver he wants to - use for this example:

-
- - - - -
int main()
-{
-  // ask user for driver
-  video::E_DRIVER_TYPE driverType;
-
-
-  printf("Please select the driver you want for this example:\n"\
" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
" (d) Software Renderer\n (e) Apfelbaum Software Renderer\n"\
" (f) NullDevice\n (otherKey) exit\n\n");

char i;
std::cin >> i;
- switch(i)
{
case 'a': driverType = video::EDT_DIRECT3D9;break;
case 'b': driverType = video::EDT_DIRECT3D8;break;
case 'c': driverType = video::EDT_OPENGL; break;
case 'd': driverType = video::EDT_SOFTWARE; break;
case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
case 'f': driverType = video::EDT_NULL; break;
default: return 1;
} - - // create device and exit if creation failed - device = createDevice(driverType, core::dimension2d<s32>(640, 480));
- if (device == 0) - return 1; -
-
-

The creation was successful, now we set the event receiver and store - pointers to the driver and to the gui environment.

- - - - -
MyEventReceiver receiver;
-device->setEventReceiver(&receiver);
-device->setWindowCaption(L"Irrlicht Engine - User Inferface Demo");
-
video::IVideoDriver* driver = device->getVideoDriver();
-IGUIEnvironment* env = device->getGUIEnvironment();
-
-
-

We add three buttons. The first one closes the engine. The second creates - a window and the third opens a file open dialog. The third parameter is - the id of the button, with which we can easily identify the button in - the event receiver.

- - - - -
env->addButton(rect<s32>(10,240,100,270), 0, 101, L"Quit");
env->addButton(rect<s32>(10,280,100,320), 0, 102, L"New Window");
env->addButton(rect<s32>(10,330,100,370), 0, 103, L"File Open");
-

Now, we add a static text and a scrollbar, which modifies the transparency - of all gui elements. We set the maximum value of the scrollbar to 255, - because that's the maximal value for a color value.
- Then we create an other static text and a list box.

- - - - -
env->addStaticText(L"Transparent Control:", rect<s32>(150,20,350,40), true);
IGUIScrollBar* scrollbar = env->addScrollBar(true, - rect<s32>(150, 45, 350, 60), 0, 104);
scrollbar->setMax(255);
-
env->addStaticText(L"Logging ListBox:", rect<s32>(50,110,250,130), true);
-listbox = env->addListBox(rect<s32>(50, 140, 250, 210));
-
- To make the font a little bit nicer, we load an external font and set it - as new font in the skin. An at last, we create a nice Irrlicht Engine logo - in the top left corner.
-
- - - - -
IGUISkin* skin = env->getSkin();
IGUIFont* font = env->getFont("../../media/fonthaettenschweiler.bmp");
if (font)
skin->setFont(font);
-
IGUIImage* img = env->addImage(
driver->getTexture("../../media/irrlichtlogoalpha.tga"),
position2d<int>(10,10));
-

That's all, we only have to draw everything.

- - - - -
-
  while(device->run() && driver)
if (device->isWindowActive())
{
driver->beginScene(true, true, SColor(0,122,65,171)); - env->drawAll(); - driver->endScene(); - } - - device->drop();
-
  return 0;
-}
-
- -
-

 

- - + + + + + + + +Tutorial 5: User Interface + + + + +
+ + + +
+
+
+
Tutorial 5: User Interface
+
+
+
+005shot.jpg +
+

This tutorial shows how to use the built in User Interface of the Irrlicht Engine. It will give a brief overview and show how to create and use windows, buttons, scroll bars, static texts, and list boxes.

+

As always, we include the header files, and use the irrlicht namespaces. We also store a pointer to the Irrlicht device, a counter variable for changing the creation position of a window, and a pointer to a listbox.

#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
// Declare a structure to hold some context for the event receiver so that it
// has it available inside its OnEvent() method.
struct SAppContext
{
IrrlichtDevice *device;
s32 counter;
IGUIListBox* listbox;
};
// Define some values that we'll use to identify individual GUI controls.
enum
{
GUI_ID_QUIT_BUTTON = 101,
GUI_ID_NEW_WINDOW_BUTTON,
GUI_ID_FILE_OPEN_BUTTON,
GUI_ID_TRANSPARENCY_SCROLL_BAR
};

Set the skin transparency by changing the alpha values of all skin-colors

void setSkinTransparency(s32 alpha, irr::gui::IGUISkin * skin)
{
for (s32 i=0; i<irr::gui::EGDC_COUNT ; ++i)
{
video::SColor col = skin->getColor((EGUI_DEFAULT_COLOR)i);
col.setAlpha(alpha);
skin->setColor((EGUI_DEFAULT_COLOR)i, col);
}
}

The Event Receiver is not only capable of getting keyboard and mouse input events, but also events of the graphical user interface (gui). There are events for almost everything: button click, listbox selection change, events that say that a element was hovered and so on. To be able to react to some of these events, we create an event receiver. We only react to gui events, and if it's such an event, we get the id of the caller (the gui element which caused the event) and get the pointer to the gui environment.

class MyEventReceiver : public IEventReceiver
{
public:
MyEventReceiver(SAppContext & context) : Context(context) { }
virtual bool OnEvent(const SEvent& event)
{
if (event.EventType == EET_GUI_EVENT)
{
s32 id = event.GUIEvent.Caller->getID();
IGUIEnvironment* env = Context.device->getGUIEnvironment();
switch(event.GUIEvent.EventType)
{

If a scrollbar changed its scroll position, and it is 'our' scrollbar (the one with id GUI_ID_TRANSPARENCY_SCROLL_BAR), then we change the transparency of all gui elements. This is an easy task: There is a skin object, in which all color settings are stored. We simply go through all colors stored in the skin and change their alpha value.

case EGET_SCROLL_BAR_CHANGED:
if (id == GUI_ID_TRANSPARENCY_SCROLL_BAR)
{
s32 pos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
setSkinTransparency(pos, env->getSkin());
}
break;

If a button was clicked, it could be one of 'our' three buttons. If it is the first, we shut down the engine. If it is the second, we create a little window with some text on it. We also add a string to the list box to log what happened. And if it is the third button, we create a file open dialog, and add also this as string to the list box. That's all for the event receiver.

case EGET_BUTTON_CLICKED:
switch(id)
{
case GUI_ID_QUIT_BUTTON:
Context.device->closeDevice();
return true;
case GUI_ID_NEW_WINDOW_BUTTON:
{
Context.listbox->addItem(L"Window created");
Context.counter += 30;
if (Context.counter > 200)
Context.counter = 0;
IGUIWindow* window = env->addWindow(
rect<s32>(100 + Context.counter, 100 + Context.counter, 300 + Context.counter, 200 + Context.counter),
false, // modal?
L"Test window");
env->addStaticText(L"Please close me",
rect<s32>(35,35,140,50),
true, // border?
false, // wordwrap?
window);
}
return true;
case GUI_ID_FILE_OPEN_BUTTON:
Context.listbox->addItem(L"File open");
// There are some options for the file open dialog
// We set the title, make it a modal window, and make sure
// that the working directory is restored after the dialog
// is finished.
env->addFileOpenDialog(L"Please choose a file.", true, 0, -1, true);
return true;
default:
return false;
}
break;
case EGET_FILE_SELECTED:
{
// show the event and the selected model filename from the file dialog
IGUIFileOpenDialog* dialog =
(IGUIFileOpenDialog*)event.GUIEvent.Caller;
Context.listbox->addItem(L"EGET_FILE_SELECTED");
Context.listbox->addItem(dialog->getFileName());
}
break;
case EGET_DIRECTORY_SELECTED:
{
// show the event and the selected directory name from the file dialog
IGUIFileOpenDialog* dialog =
(IGUIFileOpenDialog*)event.GUIEvent.Caller;
Context.listbox->addItem(L"EGET_DIRECTORY_SELECTED");
Context.listbox->addItem(dialog->getDirectoryNameW());
}
break;
default:
break;
}
}
return false;
}
private:
SAppContext & Context;
};

OK, now for the more interesting part. First, create the Irrlicht device. As in some examples before, we ask the user which driver he wants to use for this example.

int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// create device and exit if creation failed
IrrlichtDevice * device = createDevice(driverType, core::dimension2d<u32>(640, 480));
if (device == 0)
return 1; // could not create selected driver.

The creation was successful, now we set the event receiver and store pointers to the driver and to the gui environment.

device->setWindowCaption(L"Irrlicht Engine - User Interface Demo");
device->setResizable(true);
video::IVideoDriver* driver = device->getVideoDriver();
IGUIEnvironment* env = device->getGUIEnvironment();
const io::path mediaPath = getExampleMediaPath();

To make the font a little bit nicer, we load an external font and set it as the new default font in the skin. To keep the standard font for tool tip text, we set it to the built-in font.

IGUISkin* skin = env->getSkin();
IGUIFont* font = env->getFont(mediaPath + "fonthaettenschweiler.bmp");
if (font)
skin->setFont(font);
skin->setFont(env->getBuiltInFont(), EGDF_TOOLTIP);

We add three buttons. The first one closes the engine. The second creates a window and the third opens a file open dialog. The third parameter is the id of the button, with which we can easily identify the button in the event receiver.

env->addButton(rect<s32>(10,240,110,240 + 32), 0, GUI_ID_QUIT_BUTTON,
L"Quit", L"Exits Program");
env->addButton(rect<s32>(10,280,110,280 + 32), 0, GUI_ID_NEW_WINDOW_BUTTON,
L"New Window", L"Launches a new Window");
env->addButton(rect<s32>(10,320,110,320 + 32), 0, GUI_ID_FILE_OPEN_BUTTON,
L"File Open", L"Opens a file");

Now, we add a static text and a scrollbar, which modifies the transparency of all gui elements. We set the maximum value of the scrollbar to 255, because that's the maximal value for a color value. Then we create an other static text and a list box.

env->addStaticText(L"Transparent Control:", rect<s32>(150,20,350,40), true);
IGUIScrollBar* scrollbar = env->addScrollBar(true,
rect<s32>(150, 45, 350, 60), 0, GUI_ID_TRANSPARENCY_SCROLL_BAR);
scrollbar->setMax(255);
scrollbar->setPos(255);
setSkinTransparency( scrollbar->getPos(), env->getSkin());
// set scrollbar position to alpha value of an arbitrary element
scrollbar->setPos(env->getSkin()->getColor(EGDC_WINDOW).getAlpha());
env->addStaticText(L"Logging ListBox:", rect<s32>(10,110,350,130), true);
IGUIListBox * listbox = env->addListBox(rect<s32>(10, 140, 350, 210));
env->addEditBox(L"Editable Text", rect<s32>(350, 80, 550, 100));
// Store the appropriate data in a context structure.
SAppContext context;
context.device = device;
context.counter = 0;
context.listbox = listbox;
// Then create the event receiver, giving it that context structure.
MyEventReceiver receiver(context);
// And tell the device to use our custom event receiver.
device->setEventReceiver(&receiver);

And at last, we create a nice Irrlicht Engine logo in the top left corner.

env->addImage(driver->getTexture(mediaPath + "irrlichtlogo3.png"),
position2d<int>(10,10));

That's all, we only have to draw everything.

while(device->run() && driver)
if (device->isWindowActive())
{
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, SColor(0,200,200,200));
env->drawAll();
driver->endScene();
}
device->drop();
return 0;
}
+ + +

 

+ + diff --git a/examples/06.2DGraphics/tutorial.html b/examples/06.2DGraphics/tutorial.html index ea6291af..423c9c25 100644 --- a/examples/06.2DGraphics/tutorial.html +++ b/examples/06.2DGraphics/tutorial.html @@ -1,163 +1,208 @@ - - -Irrlicht Engine Tutorial - - - - -
- - - - - - - - -
-
-
-
Tutorial 6. 2D Graphics
-
-
-
-

This Tutorial shows how to do 2d graphics with the Irrlicht Engine. - It shows how to draw images, keycolor based sprites, transparent rectangles - and different fonts. You will may consider this useful if you want to - make a 2d game with the engine, or if you want to draw a cool interface - or head up display for your 3d game.

-

The program which is described here will look like this:

-


-

-
-
-
- - - - - - - -
- Lets start!
-

As always, I include the header files, use the irr namespace, and tell - the linker to link with the .lib file.

- - - - -
#include <irrlicht.h>
#include <iostream>

using namespace irr;
-
#pragma comment(lib, "Irrlicht.lib")
-               
-

At first, we let the user select the driver type, then start up the - engine, set a caption, and get a pointer to the video driver.

- - - - -
int main()
{
// let user select driver type
video::E_DRIVER_TYPE driverType;

printf("Please select the driver you want for this example:\n"\
" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
" (d) Software Renderer\n (e) Apfelbaum Software Renderer\n"\
" (f) NullDevice\n (otherKey) exit\n\n");

char i;
std::cin >> i;

switch(i)
{
case 'a': driverType = video::EDT_DIRECT3D9;break;
case 'b': driverType = video::EDT_DIRECT3D8;break;
case 'c': driverType = video::EDT_OPENGL; break;
case 'd': driverType = video::EDT_SOFTWARE; break;
case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
case 'f': driverType = video::EDT_NULL; break;
default: return 0;
}

// create device
-
  IrrlichtDevice *device = createDevice(driverType,
-         core::dimension2d<s32>(512, 384));
-
  if (device == 0)
-      return 1;
- 
device->setWindowCaption(L"Irrlicht Engine - 2D Graphics Demo");
-
  video::IVideoDriver* driver = device->getVideoDriver();
-

All 2d graphics in this example are put together into one texture, - 2ddemo.bmp. Because we want to draw colorkey based sprites, we need - to load this texture and tell the engine, which part of it should be - transparent based on a colorkey. In this example, we don't tell it the - color directly, we just say "Hey Irrlicht Engine, you'll find the - color I want at position (0,0) on the texture.". Instead, it would - be also possible to call driver->makeColorKeyTexture(images, - video::SColor(0,0,0,0)), to make e.g. all black pixels transparent. - Please note, that makeColorKeyTexture just creates an alpha channel - based on the color.

- - - - -
video::ITexture* images = driver->getTexture("../../media/2ddemo.bmp");
driver->makeColorKeyTexture(images, core::position2d<s32>(0,0));
-

To be able to draw some text with two different fonts, we load them. - Ok, we load just one, as first font we just use the default font which - is built into the engine.
- Also, we define two rectangles, which specify the position of the images - of the red imps (little flying creatures) in the texture.

- - - - -
gui::IGUIFont* font = device->getGUIEnvironment()->getBuiltInFont();
gui::IGUIFont* font2 = device->getGUIEnvironment()->getFont( - "../../media/fonthaettenschweiler.bmp");
-
core::rect<s32> imp1(349,15,385,78);
-core::rect<s32> imp2(387,15,423,78);
-

Everything is prepared, now we can draw everything in the draw loop, - between the begin scene and end scene calls. In this example, we are - just doing 2d graphics, but it would be no problem to mix them with - 3d graphics. Just try it out, and draw some 3d vertices or set up a - scene with the scene manager and draw it.

-
- - - - -
while(device->run() && driver)
{
if (device->isWindowActive())
{
u32 time = device->getTimer()->getTime();
driver->beginScene(true, true, video::SColor(0,120,102,136)); -
-

First, we draw 3 sprites, using the alpha channel we created with makeColorKeyTexture. - The last parameter specifiys that the drawing method should use thiw alpha - channel. The parameter before the last one specifies a color, with wich - the sprite should be colored. (255,255,255,255) is full white, so the - sprite will look like the original. The third sprite is drawed colored - based on the time.

- - - - -
// draw fire & dragons background world
driver->draw2DImage(images, core::position2d<s32>(50,50),
core::rect<s32>(0,0,342,224), 0,
video::SColor(255,255,255,255), true);
-
// draw flying imp 
-driver->draw2DImage(images, core::position2d<s32>(164,125),
-  (time/500 % 2) ? imp1 : imp2, 0, 
-   video::SColor(255,255,255,255), true);
-
// draw second flying imp with colorcylce
-driver->draw2DImage(images, core::position2d<s32>(270,105),
-  (time/500 % 2) ? imp1 : imp2, 0, 
-  video::SColor(255,(time) % 255,255,255), true);
-

Drawing text is really simple. The code should be self explanatory.

- - - - -
// draw some text
if (font)
font->draw(L"This is some text.",
core::rect<s32>(130,10,300,50),
video::SColor(255,255,255,255));
-
// draw some other text
-if (font2)
-   font2->draw(L"This is some other text.", 
-       core::rect<s32>(130,20,300,60),
-       video::SColor(255,time % 255,time % 255,255));
-

At last, we draw the Irrlicht Engine logo (without using a color or an - alpha channel) and a transparent 2d Rectangle at the position of the mouse - cursor.

- - - - -
    // draw logo
driver->draw2DImage(images, core::position2d<s32>(10,10),
core::rect<s32>(354,87,442,118));
-
    // draw transparent rect under cursor
-    core::position2d<s32> m = device->getCursorControl()->getPosition();
-    driver->draw2DRectangle(video::SColor(100,255,255,255),
-          core::rect<s32>(m.X-20, m.Y-20, m.X+20, m.Y+20));
-
    driver->endScene();
-  }
-}
-

That's all, it was not really difficult, I hope.

- - - - -
   device->drop();
-   return 0;
-}
-
-

 

-

 

- - + + + + + + + +Tutorial 6: 2D Graphics + + + + +
+ + + +
+
+
+
Tutorial 6: 2D Graphics
+
+
+
+006shot.jpg +
+

This tutorial shows how to do 2d graphics with the Irrlicht Engine. It shows how to draw images, keycolor based sprites, transparent rectangles, and different fonts. You may consider this useful if you want to make a 2d game with the engine, or if you want to draw a cool interface or head up display for your 3d game.

+

As always, I include the header files, use the irr namespace, and tell the linker to link with the .lib file.

#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

At first, we let the user select the driver type, then start up the engine, set a caption, and get a pointer to the video driver.

int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// create device
IrrlichtDevice *device = createDevice(driverType,
core::dimension2d<u32>(512, 384));
if (device == 0)
return 1; // could not create selected driver.
device->setWindowCaption(L"Irrlicht Engine - 2D Graphics Demo");
video::IVideoDriver* driver = device->getVideoDriver();
const io::path mediaPath = getExampleMediaPath();

All 2d graphics in this example are put together into one texture, 2ddemo.png. Because we want to draw colorkey based sprites, we need to load this texture and tell the engine, which part of it should be transparent based on a colorkey.

+

In this example, we don't tell it the color directly, we just say "Hey Irrlicht Engine, you'll find the color I want at position (0,0) on the texture.". Instead, it would be also possible to call driver->makeColorKeyTexture(images, video::SColor(0,0,0,0)), to make e.g. all black pixels transparent. Please note that makeColorKeyTexture just creates an alpha channel based on the color.

video::ITexture* images = driver->getTexture(mediaPath + "2ddemo.png");
driver->makeColorKeyTexture(images, core::position2d<s32>(0,0));

To be able to draw some text with two different fonts, we first load them. OK, we load just one. As the first font we just use the default font which is built into the engine. Also, we define two rectangles which specify the position of the images of the red imps (little flying creatures) in the texture.

gui::IGUIFont* font = device->getGUIEnvironment()->getBuiltInFont();
gui::IGUIFont* font2 =
device->getGUIEnvironment()->getFont(mediaPath + "fonthaettenschweiler.bmp");
core::rect<s32> imp1(349,15,385,78);
core::rect<s32> imp2(387,15,423,78);

Prepare a nicely filtering 2d render mode for special cases.

driver->getMaterial2D().TextureLayer[0].BilinearFilter=true;
driver->getMaterial2D().AntiAliasing=video::EAAM_FULL_BASIC;

Everything is prepared, now we can draw everything in the draw loop, between the begin scene and end scene calls. In this example, we are just doing 2d graphics, but it would be no problem to mix them with 3d graphics. Just try it out, and draw some 3d vertices or set up a scene with the scene manager and draw it.

while(device->run() && driver)
{
if (device->isWindowActive())
{
u32 time = device->getTimer()->getTime();
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(255,120,102,136));

First, we draw 3 sprites, using the alpha channel we created with makeColorKeyTexture. The last parameter specifies that the drawing method should use this alpha channel. The last-but-one parameter specifies a color, with which the sprite should be colored. (255,255,255,255) is full white, so the sprite will look like the original. The third sprite is drawn with the red channel modulated based on the time.

// draw fire & dragons background world
driver->draw2DImage(images, core::position2d<s32>(50,50),
core::rect<s32>(0,0,342,224), 0,
video::SColor(255,255,255,255), true);
// draw flying imp
driver->draw2DImage(images, core::position2d<s32>(164,125),
(time/500 % 2) ? imp1 : imp2, 0,
video::SColor(255,255,255,255), true);
// draw second flying imp with color cycle
driver->draw2DImage(images, core::position2d<s32>(270,105),
(time/500 % 2) ? imp1 : imp2, 0,
video::SColor(255,(time) % 255,255,255), true);

Drawing text is really simple. The code should be self explanatory.

// draw some text
if (font)
font->draw(L"This demo shows that Irrlicht is also capable of drawing 2D graphics.",
core::rect<s32>(130,10,300,50),
video::SColor(255,255,255,255));
// draw some other text
if (font2)
font2->draw(L"Also mixing with 3d graphics is possible.",
core::rect<s32>(130,20,300,60),
video::SColor(255,time % 255,time % 255,255));

Next, we draw the Irrlicht Engine logo (without using a color or an alpha channel). Since we slightly scale the image we use the prepared filter mode.

driver->enableMaterial2D();
driver->draw2DImage(images, core::rect<s32>(10,10,108,48),
core::rect<s32>(354,87,442,118));
driver->enableMaterial2D(false);

Finally draw a half-transparent rect under the mouse cursor.

core::position2d<s32> m = device->getCursorControl()->getPosition();
driver->draw2DRectangle(video::SColor(100,255,255,255),
core::rect<s32>(m.X-20, m.Y-20, m.X+20, m.Y+20));
driver->endScene();
}
}
device->drop();
return 0;
}

That's all. I hope it was not too difficult.

+
+ + +

 

+ + diff --git a/examples/07.Collision/tutorial.html b/examples/07.Collision/tutorial.html index c04fce99..03fa80af 100644 --- a/examples/07.Collision/tutorial.html +++ b/examples/07.Collision/tutorial.html @@ -1,308 +1,207 @@ - - -Irrlicht Engine Tutorial - - - - -
- - - - - - - - -
-
-
-
Tutorial 7. Collision detection - and response
-
-
-
-

In this tutorial, I will show how to collision detection with the Irrlicht - Engine. I will describe 3 methods: Automatic collision detection for - moving through 3d worlds with stair climbing and sliding, manual triangle - picking and manual scene node picking.

-

The program which is described here will look like this:

-


-

-
-
-
- - - - - - - -
- Lets start!
-
-

To start, we take the program from tutorial 2, which loaded and displayed - a quake 3 level. We will use the level to walk in it and to pick triangles - from it. In addition we'll place 3 animated models into it for scene - node picking. The following code starts up the engine and loads a - quake 3 level. I will not explain it, because it should already be - known from tutorial 2.

- - - - -
#include <irrlicht.h>
-#include <iostream>
-
using namespace irr; - -#pragma comment(lib, "Irrlicht.lib") - -int main() -{ - // let user select driver type -
video::E_DRIVER_TYPE driverType;

printf("Please select the driver you want for this example:\n"\
" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
" (d) Software Renderer\n (e) Apfelbaum Software Renderer\n"\
" (f) NullDevice\n (otherKey) exit\n\n");

char i;
std::cin >> i;

switch(i)
{
case 'a': driverType = video::EDT_DIRECT3D9;break;
case 'b': driverType = video::EDT_DIRECT3D8;break;
case 'c': driverType = video::EDT_OPENGL; break;
case 'd': driverType = video::EDT_SOFTWARE; break;
case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
case 'f': driverType = video::EDT_NULL; break;
default: return 0;
}
- // create device
-
  IrrlichtDevice *device = createDevice(driverType,
-     core::dimension2d<s32>(640, 480), 16, false);
- if (device == 0)
return 1; // could not create selected driver.

video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();

device->getFileSystem()->addZipFileArchive
("../../media/map-20kdm2.pk3"); - - - scene::IAnimatedMesh* q3levelmesh = smgr->getMesh("20kdm2.bsp"); - scene::ISceneNode* q3node = 0; - - if (q3levelmesh) - q3node = smgr->addOctTreeSceneNode(q3levelmesh->getMesh(0)); -
-
-

So far so good, we've loaded the quake 3 level like in tutorial - 2. Now, here comes something different: We create a triangle selector. - A triangle selector is a class which can fetch the triangles from - scene nodes for doing different things with them, for example collision - detection. There are different triangle selectors, and all can be - created with the ISceneManager. In this example, we create an OctTreeTriangleSelector, - which optimizes the triangle output a little bit by reducing it like - an octree. This is very useful for huge meshes like quake 3 levels.
- Afte we created the triangle selector, we attach it to the q3node. - This is not necessary, but in this way, we do not need to care for - the selector, for example dropping it after we do not need it anymore.

- - - - -
scene::ITriangleSelector* selector = 0;
-	
-	if (q3node)
-	{		
-		q3node->setPosition(core::vector3df(-1370,-130,-1400));
-
-		selector = smgr->createOctTreeTriangleSelector(
-            q3levelmesh->getMesh(0), q3node, 128);
-		q3node->setTriangleSelector(selector);
-	}
-

We add a first person shooter camera to the scene for being able - to move in the quake 3 level like in tutorial 2. But this, time, we - add a special animator to the camera: A Collision Response animator. - This thing modifies the scene node to which it is attached to in that - way, that it may no more move through walls and is affected by gravity. - The only thing we have to tell the animator is how the world looks - like, how big the scene node is, how gravity and so on. After the - collision response animator is attached to the camera, we do not have - to do anything more for collision detection, anything is done automaticly, - all other collision detection code below is for picking. And please - note another cool feature: The collsion response animator can be attached - also to all other scene nodes, not only to cameras. And it can be - mixed with other scene node animators. In this way, collision detection - and response in the Irrlicht
- engine is really, really easy.
- Now we'll take a closer look on the parameters of createCollisionResponseAnimator(). - The first parameter is the TriangleSelector, which specifies how the - world, against collision detection is done looks like. The second - parameter is the scene node, which is the object, which is affected - by collision detection, in our case it is the camera. The third defines - how big the object is, it is the radius of an ellipsoid. Try it out - and change the radius to smaller values, the camera will be able to - move closer to walls after this. The next parameter is the direction - and speed of gravity. You could set it to (0,0,0) to disable gravity. - And the last value is just a translation: Without this, the ellipsoid - with which collision detection is done would be around the camera, - and the camera would be in the middle of the ellipsoid. But as human - beings, we are used to have our eyes on top of the body, with which - we collide with our world, not in the middle of it. So we place the - scene node 50 units over the center of the ellipsoid with this parameter. - And that's it, collision detection works now.
-

- - - - -
	scene::ICameraSceneNode* camera = 	
camera = smgr->addCameraSceneNodeFPS(0,100.0f,300.0f); - camera->setPosition(core::vector3df(-100,50,-150)); - - scene::ISceneNodeAnimator* anim =
smgr->createCollisionResponseAnimator( - selector, camera, core::vector3df(30,50,30), - core::vector3df(0,-3,0), - core::vector3df(0,50,0));
- selector->drop();
- camera->addAnimator(anim); - anim->drop();
-

Because collision detection is no big deal in irrlicht, I'll describe - how to do two different types of picking in the next section. But - before this, I'll prepare the scene a little. I need three animated - characters which we
- could pick later, a dynamic light for lighting them, a billboard for - drawing where we found an intersection, and, yes, I need to get rid - of this mouse cursor. :)

- - - - -
	// disable mouse cursor
-
-	device->getCursorControl()->setVisible(false);
-
-	// add billboard
-
-	scene::IBillboardSceneNode * bill = smgr->addBillboardSceneNode();
-	bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR );
-	bill->setMaterialTexture(0, driver->getTexture(
"../../media/particle.bmp")); - bill->setMaterialFlag(video::EMF_LIGHTING, false); - bill->setSize(core::dimension2d<f32>(20.0f, 20.0f)); - - // add 3 animated faeries. - - video::SMaterial material; - material.Texture1 = driver->getTexture(
"../../media/faerie2.bmp"
); - material.Lighting = true; - - scene::IAnimatedMeshSceneNode* node = 0; - scene::IAnimatedMesh* faerie = smgr->getMesh(
"../../media/faerie.md2"); - - if (faerie) - { - node = smgr->addAnimatedMeshSceneNode(faerie); - node->setPosition(core::vector3df(-70,0,-90)); - node->setMD2Animation(scene::EMAT_RUN); - node->getMaterial(0) = material; - - node = smgr->addAnimatedMeshSceneNode(faerie); - node->setPosition(core::vector3df(-70,0,-30)); - node->setMD2Animation(scene::EMAT_SALUTE); - node->getMaterial(0) = material; - - node = smgr->addAnimatedMeshSceneNode(faerie); - node->setPosition(core::vector3df(-70,0,-60)); - node->setMD2Animation(scene::EMAT_JUMP); - node->getMaterial(0) = material; - } - - material.Texture1 = 0; - material.Lighting = false; - - // Add a light - - smgr->addLightSceneNode(0, core::vector3df(-60,100,400), - video::SColorf(1.0f,1.0f,1.0f,1.0f), - 600.0f);
-

For not making it to complicated, I'm doing picking inside the drawing - loop. We take two pointers for storing the current and the last selected - scene node and start the loop.

-
- - - - -
	scene::ISceneNode* selectedSceneNode = 0;
-	scene::ISceneNode* lastSelectedSceneNode = 0;
-
-	
-	int lastFPS = -1;
-
-	while(device->run())
if (device->isWindowActive()) - { - driver->beginScene(true, true, 0); - - smgr->drawAll();
-

After we've drawn the whole scene whit smgr->drawAll(), we'll do - the first picking: We want to know which triangle of the world we are - looking at. In addition, we want the exact point of the quake 3 level - we are looking at. For this, we create a 3d line starting at the position - of the camera and going through the lookAt-target of it. Then we ask - the collision manager if this line collides with a triangle of the world - stored in the triangle selector. If yes, we draw the 3d triangle and - set the position of the billboard to the intersection point.

- - - - -
		core::line3d<f32> line;
-		line.start = camera->getPosition();
-		line.end = line.start +
-         (camera->getTarget() - line.start).normalize() * 1000.0f;
-
-		core::vector3df intersection;
-		core::triangle3df tri;
-
-		if (smgr->getSceneCollisionManager()->getCollisionPoint(
-			line, selector, intersection, tri))
-		{
-			bill->setPosition(intersection);
-				
-			driver->setTransform(video::ETS_WORLD, core::matrix4());
-			driver->setMaterial(material);
-			driver->draw3DTriangle(tri, video::SColor(0,255,0,0));
-		}
-

Another type of picking supported by the Irrlicht Engine is scene - node picking based on bouding boxes. Every scene node has got a bounding - box, and because of that, it's very fast for example to get the scene - node which the camera looks
- at. Again, we ask the collision manager for this, and if we've got a - scene node, we highlight it by disabling Lighting in its material, if - it is not the billboard or the quake 3 level.

- - - - -
		selectedSceneNode = smgr->getSceneCollisionManager()->
-          getSceneNodeFromCameraBB(camera);
-
-		if (lastSelectedSceneNode)
-			lastSelectedSceneNode->setMaterialFlag(
-                video::EMF_LIGHTING, true);
-
-		if (selectedSceneNode == q3node ||
-           selectedSceneNode == bill)
-			selectedSceneNode = 0;
-
-		if (selectedSceneNode)
-			selectedSceneNode->setMaterialFlag(
-               video::EMF_LIGHTING, false);
-
-		lastSelectedSceneNode = selectedSceneNode;
-

That's it, we just have to finish drawing.

- - - - -
		driver->endScene();
-
-		int fps = driver->getFPS();
-
-		if (lastFPS != fps)
-		{
-		  core::stringw str = L"Collision detection example - Irrlicht Engine [";
str += driver->getName();
str += "] FPS:";
str += fps;

device->setWindowCaption(str.c_str());
lastFPS = fps;
} - } - - device->drop(); - - return 0; -} - -
-

 

-

 

-
-
-

 

- - + + + + + + + +Tutorial 7: Collision + + + + +
+ + + +
+
+
+
Tutorial 7: Collision
+
+
+
+007shot.jpg +
+

We will describe 2 methods: Automatic collision detection for moving through 3d worlds with stair climbing and sliding, and manual scene node and triangle picking using a ray. In this case, we will use a ray coming out from the camera, but you can use any ray.

+

To start, we take the program from tutorial 2, which loads and displays a quake 3 level. We will use the level to walk in it and to pick triangles from. In addition we'll place 3 animated models into it for triangle picking. The following code starts up the engine and loads the level, as per tutorial 2.

#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
enum
{
// I use this ISceneNode ID to indicate a scene node that is
// not pickable by getSceneNodeAndCollisionPointFromRay()
ID_IsNotPickable = 0,
// I use this flag in ISceneNode IDs to indicate that the
// scene node can be picked by ray selection.
IDFlag_IsPickable = 1 << 0,
// I use this flag in ISceneNode IDs to indicate that the
// scene node can be highlighted. In this example, the
// homonids can be highlighted, but the level mesh can't.
IDFlag_IsHighlightable = 1 << 1
};
int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// create device
IrrlichtDevice *device =
createDevice(driverType, core::dimension2d<u32>(640, 480), 16, false);
if (device == 0)
return 1; // could not create selected driver.

If we want to receive information about the material of a hit triangle we have to get collisions per meshbuffer. The only disadvantage of this is that getting them per meshbuffer can be a little bit slower than per mesh, but usually that's not noticeable. If you set this to false you will no longer get material names in the title bar.

const bool separateMeshBuffers = true;
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
const io::path mediaPath = getExampleMediaPath();
device->getFileSystem()->addFileArchive(mediaPath + "map-20kdm2.pk3");
scene::IAnimatedMesh* q3levelmesh = smgr->getMesh("20kdm2.bsp");
scene::IMeshSceneNode* q3node = 0;
// The Quake mesh is pickable, but doesn't get highlighted.
if (q3levelmesh)
q3node = smgr->addOctreeSceneNode(q3levelmesh->getMesh(0), 0, IDFlag_IsPickable);

So far so good, we've loaded the quake 3 level like in tutorial 2. Now, here comes something different: We create a triangle selector. A triangle selector is a class which can fetch the triangles from scene nodes for doing different things with them, for example collision detection. There are different triangle selectors, and all can be created with the ISceneManager. In this example, we create an OctreeTriangleSelector, which optimizes the triangle output a little bit by reducing it like an octree. This is very useful for huge meshes like quake 3 levels. After we created the triangle selector, we attach it to the q3node. This is not necessary, but in this way, we do not need to care for the selector, for example dropping it after we do not need it anymore.

scene::ITriangleSelector* selector = 0;
if (q3node)
{
q3node->setPosition(core::vector3df(-1350,-130,-1400));

There is currently no way to split an octree by material. So if we need material infos we have to create one octree per meshbuffer and put them together in a MetaTriangleSelector.

if ( separateMeshBuffers && q3node->getMesh()->getMeshBufferCount() > 1)
{
scene::IMetaTriangleSelector * metaSelector = smgr->createMetaTriangleSelector();
for ( irr::u32 m=0; m < q3node->getMesh()->getMeshBufferCount(); ++m )
{
scene::ITriangleSelector*
bufferSelector = smgr->createOctreeTriangleSelector(
q3node->getMesh()->getMeshBuffer(m), m, q3node);
if ( bufferSelector )
{
metaSelector->addTriangleSelector( bufferSelector );
bufferSelector->drop();
}
}
selector = metaSelector;
}
else
{
// If you don't need material infos just create one octree for the
// whole mesh.
selector = smgr->createOctreeTriangleSelector(
q3node->getMesh(), q3node, 128);
}
q3node->setTriangleSelector(selector);
// We're not done with this selector yet, so don't drop it.
}

We add a first person shooter camera to the scene so that we can see and move in the quake 3 level like in tutorial 2. But this, time, we add a special animator to the camera: A collision response animator. This animator modifies the scene node to which it is attached in order to prevent it from moving through walls and to add gravity to the node. The only things we have to tell the animator is how the world looks like, how big the scene node is, how much gravity to apply and so on. After the collision response animator is attached to the camera, we do not have to do anything else for collision detection, it's all done automatically. The rest of the collision detection code below is for picking. And please note another cool feature: The collision response animator can be attached also to all other scene nodes, not only to cameras. And it can be mixed with other scene node animators. In this way, collision detection and response in the Irrlicht engine is really easy.

+

Now we'll take a closer look on the parameters of createCollisionResponseAnimator(). The first parameter is the TriangleSelector, which specifies how the world, against which collision detection is done, looks like. The second parameter is the scene node, which is the object which is affected by collision detection - in our case it is the camera. The third defines how big the object is, it is the radius of an ellipsoid. Try it out and change the radius to smaller values, the camera will be able to move closer to walls after this. The next parameter is the direction and speed of gravity. We'll set it to (0, -1000, 0), which approximates realistic gravity (depends on the units which are used in the scene model). You could set it to (0,0,0) to disable gravity. And the last value is just an offset: Without it the ellipsoid with which collision detection is done would be around the camera and the camera would be in the middle of the ellipsoid. But as human beings, we are used to have our eyes on top of the body, not in the middle of it. So we place the scene node 50 units over the center of the ellipsoid with this parameter. And that's it, collision detection works now.

// Set a jump speed of 300 units per second, which gives a fairly realistic jump
// when used with the gravity of (0, -1000, 0) in the collision response animator.
scene::ICameraSceneNode* camera =
smgr->addCameraSceneNodeFPS(0, 100.0f, .3f, ID_IsNotPickable, 0, 0, true, 300.f);
camera->setPosition(core::vector3df(50,50,-60));
camera->setTarget(core::vector3df(-70,30,-60));
if (selector)
{
scene::ISceneNodeAnimatorCollisionResponse * anim = smgr->createCollisionResponseAnimator(
selector, camera, core::vector3df(30,50,30),
core::vector3df(0,-1000,0), core::vector3df(0,30,0));
selector->drop(); // As soon as we're done with the selector, drop it.
camera->addAnimator(anim);
anim->drop(); // And likewise, drop the animator when we're done referring to it.
}
// Now I create three animated characters which we can pick, a dynamic light for
// lighting them, and a billboard for drawing where we found an intersection.
// First, let's get rid of the mouse cursor. We'll use a billboard to show
// what we're looking at.
device->getCursorControl()->setVisible(false);
// Add the billboard.
scene::IBillboardSceneNode * bill = smgr->addBillboardSceneNode();
bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR );
bill->setMaterialTexture(0, driver->getTexture(mediaPath + "particle.bmp"));
bill->setMaterialFlag(video::EMF_LIGHTING, false);
bill->setMaterialFlag(video::EMF_ZBUFFER, false);
bill->setSize(core::dimension2d<f32>(20.0f, 20.0f));
bill->setID(ID_IsNotPickable); // This ensures that we don't accidentally ray-pick it

Add 3 animated hominids, which we can pick using a ray-triangle intersection. They all animate quite slowly, to make it easier to see that accurate triangle selection is being performed.

scene::IAnimatedMeshSceneNode* node = 0;
video::SMaterial material;
// Add an MD2 node, which uses vertex-based animation.
node = smgr->addAnimatedMeshSceneNode(smgr->getMesh(mediaPath + "faerie.md2"),
0, IDFlag_IsPickable | IDFlag_IsHighlightable);
node->setPosition(core::vector3df(-90,-15,-140)); // Put its feet on the floor.
node->setScale(core::vector3df(1.6f)); // Make it appear realistically scaled
node->setMD2Animation(scene::EMAT_POINT);
node->setAnimationSpeed(20.f);
material.setTexture(0, driver->getTexture(mediaPath + "faerie2.bmp"));
material.Lighting = true;
material.NormalizeNormals = true;
node->getMaterial(0) = material;
// Now create a triangle selector for it. The selector will know that it
// is associated with an animated node, and will update itself as necessary.
selector = smgr->createTriangleSelector(node, separateMeshBuffers);
node->setTriangleSelector(selector);
selector->drop(); // We're done with this selector, so drop it now.
// And this B3D file uses skinned skeletal animation.
node = smgr->addAnimatedMeshSceneNode(smgr->getMesh(mediaPath + "ninja.b3d"),
0, IDFlag_IsPickable | IDFlag_IsHighlightable);
node->setScale(core::vector3df(10));
node->setPosition(core::vector3df(-75,-66,-80));
node->setRotation(core::vector3df(0,90,0));
node->setAnimationSpeed(8.f);
node->getMaterial(0).NormalizeNormals = true;
node->getMaterial(0).Lighting = true;
// Just do the same as we did above.
selector = smgr->createTriangleSelector(node, separateMeshBuffers);
node->setTriangleSelector(selector);
selector->drop();
// This X files uses skeletal animation, but without skinning.
node = smgr->addAnimatedMeshSceneNode(smgr->getMesh(mediaPath + "dwarf.x"),
0, IDFlag_IsPickable | IDFlag_IsHighlightable);
node->setPosition(core::vector3df(-70,-66,-30)); // Put its feet on the floor.
node->setRotation(core::vector3df(0,-90,0)); // And turn it towards the camera.
node->setAnimationSpeed(20.f);
node->getMaterial(0).Lighting = true;
selector = smgr->createTriangleSelector(node, separateMeshBuffers);
node->setTriangleSelector(selector);
selector->drop();
// And this mdl file uses skinned skeletal animation.
node = smgr->addAnimatedMeshSceneNode(smgr->getMesh(mediaPath + "yodan.mdl"),
0, IDFlag_IsPickable | IDFlag_IsHighlightable);
node->setPosition(core::vector3df(-90,-25,20));
node->setScale(core::vector3df(0.8f));
node->getMaterial(0).Lighting = true;
node->setAnimationSpeed(20.f);
// Just do the same as we did above.
selector = smgr->createTriangleSelector(node, separateMeshBuffers);
node->setTriangleSelector(selector);
selector->drop();
material.setTexture(0, 0);
material.Lighting = false;
// Add a light, so that the unselected nodes aren't completely dark.
scene::ILightSceneNode * light = smgr->addLightSceneNode(0, core::vector3df(-60,100,400),
video::SColorf(1.0f,1.0f,1.0f,1.0f), 600.0f);
light->setID(ID_IsNotPickable); // Make it an invalid target for selection.
// Remember which scene node is highlighted
scene::ISceneNode* highlightedSceneNode = 0;
scene::ISceneCollisionManager* collMan = smgr->getSceneCollisionManager();
// draw the selection triangle only as wireframe
material.Wireframe=true;
while(device->run())
if (device->isWindowActive())
{
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(0));
smgr->drawAll();
// Unlight any currently highlighted scene node
if (highlightedSceneNode)
{
highlightedSceneNode->setMaterialFlag(video::EMF_LIGHTING, true);
highlightedSceneNode = 0;
}
// All intersections in this example are done with a ray cast out from the camera to
// a distance of 1000. You can easily modify this to check (e.g.) a bullet
// trajectory or a sword's position, or create a ray from a mouse click position using
// ISceneCollisionManager::getRayFromScreenCoordinates()
core::line3d<f32> ray;
ray.start = camera->getPosition();
ray.end = ray.start + (camera->getTarget() - ray.start).normalize() * 1000.0f;
// This call is all you need to perform ray/triangle collision on every scene node
// that has a triangle selector, including the Quake level mesh. It finds the nearest
// collision point/triangle, and returns the scene node containing that point.
// Irrlicht provides other types of selection, including ray/triangle selector,
// ray/box and ellipse/triangle selector, plus associated helpers.
// You might also want to check the other methods of ISceneCollisionManager.
irr::io::SNamedPath hitTextureName;
scene::SCollisionHit hitResult;
scene::ISceneNode * selectedSceneNode =collMan->getSceneNodeAndCollisionPointFromRay(
hitResult, // Returns all kind of info about the collision
ray,
IDFlag_IsPickable, // This ensures that only nodes that we have
// set up to be pickable are considered
0); // Check the entire scene (this is actually the implicit default)
// If the ray hit anything, move the billboard to the collision position
// and draw the triangle that was hit.
if(selectedSceneNode)
{
bill->setPosition(hitResult.Intersection); // Show the current intersection point with the level or a mesh
// We need to reset the transform before doing our own rendering.
driver->setTransform(video::ETS_WORLD, core::matrix4());
driver->setMaterial(material);
driver->draw3DTriangle(hitResult.Triangle, video::SColor(0,255,0,0)); // Show which triangle has been hit
// We can check the flags for the scene node that was hit to see if it should be
// highlighted. The animated nodes can be highlighted, but not the Quake level mesh
if((selectedSceneNode->getID() & IDFlag_IsHighlightable) == IDFlag_IsHighlightable)
{
highlightedSceneNode = selectedSceneNode;
// Highlighting in this case means turning lighting OFF for this node,
// which means that it will be drawn with full brightness.
highlightedSceneNode->setMaterialFlag(video::EMF_LIGHTING, false);
}
// When separateMeshBuffers is set to true we can now find out which material was hit
if ( hitResult.MeshBuffer && hitResult.Node && hitResult.Node->getMaterial(hitResult.MaterialIndex).TextureLayer[0].Texture )
{
// Note we are interested in the node material and not in the meshbuffer material.
// Otherwise we wouldn't get the fairy2 texture which is only set on the node.
hitTextureName = hitResult.Node->getMaterial(hitResult.MaterialIndex).TextureLayer[0].Texture->getName();
}
}
// We're all done drawing, so end the scene.
driver->endScene();
// Show some info in title-bar
int fps = driver->getFPS();
static core::stringw lastString;
core::stringw str = L"Collision detection example - Irrlicht Engine [";
str += driver->getName();
str += "] FPS:";
str += fps;
if ( !hitTextureName.getInternalName().empty() )
{
str += " ";
irr::io::path texName(hitTextureName.getInternalName());
str += core::deletePathFromFilename(texName);
}
if ( str != lastString ) // changing caption is somewhat expensive, so don't when nothing changed
{
device->setWindowCaption(str.c_str());
lastString = str;
}
}
device->drop();
return 0;
}
+ + +

 

+ + diff --git a/examples/08.SpecialFX/tutorial.html b/examples/08.SpecialFX/tutorial.html index 8c1fc51a..1c77ef6a 100644 --- a/examples/08.SpecialFX/tutorial.html +++ b/examples/08.SpecialFX/tutorial.html @@ -1,278 +1,210 @@ - - -Irrlicht Engine Tutorial - - - - -
- - - - - - - - -
-
-
Tutorial 8. Special Effects
-
-
-
-

This tutorials describes how to do special effects. It shows how to - use stencil buffer shadows, the particle system, billboards, dynamic - light and the water surface scene node.

-

The program which is described here will look like this:

-


-

-
-
-
- - - - - - - -
Lets start!
-
-

We start like in some tutorials before. Please note that this time, - the 'shadows' flag in createDevice() is set to true, for we want to - have a dynamic shadow casted from an animated character. If your this - example runs to slow, set it to false. The Irrlicht Engine checks - if your hardware doesn't support the stencil buffer, and disables - shadows by itself, but just in case the demo runs slow on your hardware.

- - - - -
#include <irrlicht.h>
-#include <iostream>
-
using namespace irr; - -#pragma comment(lib, "Irrlicht.lib") - -int main() -{ - // ask user for driver
video::E_DRIVER_TYPE driverType;
- printf("Please select the driver you want for this example:\n"\
" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
" (d) Software Renderer\n (e) Apfelbaum Software Renderer\n"\
" (f) NullDevice\n (otherKey) exit\n\n");

char i;
std::cin >> i;

switch(i)
{
case 'a': driverType = video::EDT_DIRECT3D9;break;
case 'b': driverType = video::EDT_DIRECT3D8;break;
case 'c': driverType = video::EDT_OPENGL; break;
case 'd': driverType = video::EDT_SOFTWARE; break;
case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
case 'f': driverType = video::EDT_NULL; break;
default: return 1;
} - - // create device and exit if creation failed
IrrlichtDevice *device = createDevice(driverType, - core::dimension2d<s32>(640, 480), 16, false, true); - - if (device == 0) - return 1; - - video::IVideoDriver* driver = device->getVideoDriver(); - scene::ISceneManager* smgr = device->getSceneManager(); -
-
-

For our environment, we load a .3ds file. It is a small room I modelled - with Anim8or and exported it into the 3ds format because the Irrlicht - Engine did not support the .an8 format when I wrote this tutorial. - I am a very bad 3d graphic artist, and so the texture mapping is not - very nice in this model. Luckily I am a better programmer than artist, - and so the Irrlicht Engine is able to create a cool texture mapping - for me: Just use the mesh manipulator and create a planar texture - mapping for the mesh. If you want to see the mapping I made with Anim8or, - uncomment this line. I also did not figure out how to
- set the material right in Anim8or, it has a specular light color - which I don't really
- like. I'll switch it off too with this code.

- - - - -
	scene::IAnimatedMesh* mesh = smgr->getMesh(
-		"../../media/room.3ds");
-
-	smgr->getMeshManipulator()->makePlanarTextureMapping(
-		mesh->getMesh(0), 0.008f);
-
-	scene::ISceneNode* node = 0;
-
-	node = smgr->addAnimatedMeshSceneNode(mesh);
-	node->setMaterialTexture(0,	driver->getTexture("../../media/wall.jpg"));
-	node->getMaterial(0).SpecularColor.set(0,0,0,0);
-

Now, for the first special effect: Animated water. It works like - this: The WaterSurfaceSceneNode takes a mesh as input and makes it - wave like a water surface. And if we let this scene node use a nice - material like the MT_REFLECTION_2_LAYER, it looks really cool. We - are doing this with the next few lines of code. As input mesh, we - create a hill plane mesh, without hills. But any other mesh could - be used for this, you could even use the room.3ds (which would look - really strange) if you wanted to.

- - - - -
	mesh = smgr->addHillPlaneMesh("myHill",
-		core::dimension2d<f32>(20,20),
-		core::dimension2d<s32>(40,40), 0, 0,
-		core::dimension2d<f32>(0,0),
-		core::dimension2d<f32>(10,10));
-
-	node = smgr->addWaterSurfaceSceneNode(mesh->getMesh(0), 3.0f, 300.0f, 30.0f);
-	node->setPosition(core::vector3df(0,7,0));
-
-	node->setMaterialTexture(0,	driver->getTexture("../../media/stones.jpg"));
-	node->setMaterialTexture(1,	driver->getTexture("../../media/water.jpg"));
-
-	node->setMaterialType(video::EMT_REFLECTION_2_LAYER);
-
-

The second special effect is very basic, I bet you saw it already - in some Irrlicht Engine demos: A transparent billboard combined with - a dynamic light. We simply create a light scene node, let it fly around, - an to make it look more cool, we attach a billboard scene node to - it.

- - - - -
	// create light
-
-	node = smgr->addLightSceneNode(0, core::vector3df(0,0,0), 
-		video::SColorf(1.0f, 0.6f, 0.7f, 1.0f), 600.0f);
-	scene::ISceneNodeAnimator* anim = 0;
-	anim = smgr->createFlyCircleAnimator (core::vector3df(0,150,0),250.0f);
-	node->addAnimator(anim);
-	anim->drop();
-
-	// attach billboard to light
-
-	node = smgr->addBillboardSceneNode(node, core::dimension2d<f32>(50, 50));
-	node->setMaterialFlag(video::EMF_LIGHTING, false);
-	node->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
-	node->setMaterialTexture(0,	driver->getTexture("../../media/particlewhite.bmp"));
-
-
-

The next special effect is a lot more interesting: A particle system. - The particle system in the Irrlicht Engine is quit modular and extensible - and yet easy to use. There is a particle system scene node into which - you can put particle emitters, which make particles come out of nothing. - These emitters are quite flexible and usually have lots of parameters - like direction, amount and color of the particles they should create.
- There are different emitters, for example a point emitter which lets - particles pop out at a fixed point. If the particle emitters available - in the engine are not enough for you, you can easily create your own - ones, you'll simply have to create a class derived from the IParticleEmitter - interface and attach it to the particle system using setEmitter().
- In this example we create a box particle emitter, which creates particles - randomly inside a box. The parameters define the box, direction of - the particles, minimal and maximal new particles per second, color - and minimal and maximal livetime of the particles.

-

Because only with emitters particle system would be a little bit - boring, there are particle affectors, which modify particles during - they fly around. They can be added to the particle system, simulating - additional effects like gravity or wind. The particle affector we - use in this example is an affector, which modifies the color of the - particles: It lets them fade out. Like the particle emitters, additional - particle affectors can also be implemented by you, simply derive a - class from IParticleAffector and add it with addAffector(). After - we set a nice material to the particle system, we have a cool looking - camp fire. By adjusting material, texture, particle emitter and affector - parameters, it is also easily possible to create smoke, rain, explosions, - snow, and so on.
-

-
- - - - -
	scene::IParticleSystemSceneNode* ps = 0;
-	ps = smgr->addParticleSystemSceneNode(false);
-	ps->setPosition(core::vector3df(-70,60,40));
-	ps->setScale(core::vector3df(2,2,2));
-
-	ps->setParticleSize(core::dimension2d<f32>(20.0f, 10.0f));
-
-	scene::IParticleEmitter* em = ps->createBoxEmitter(
-		core::aabbox3d<f32>(-7,0,-7,7,1,7), 
-		core::vector3df(0.0f,0.03f,0.0f),
-		80,100, 
-		video::SColor(0,255,255,255), video::SColor(0,255,255,255),
-		800,2000);
-
-	ps->setEmitter(em);
-	em->drop();
-
-	scene::IParticleAffector* paf = 
-		ps->createFadeOutParticleAffector();
-
-	ps->addAffector(paf);
-	paf->drop();
-
-	ps->setMaterialFlag(video::EMF_LIGHTING, false);
-	ps->setMaterialTexture(0, driver->getTexture("../../media/particle.bmp"));
-	ps->setMaterialType(video::EMT_TRANSPARENT_VERTEX_ALPHA);
-

As our last special effect, we want a dynamic shadow be casted from - an animated character. For this we load a DirectX .x model and place - it into our world. For creating the shadow, we simply need to call addShadowVolumeSceneNode(). - The color of shadows is only adjustable globally for all shadows, by - calling ISceneManager::setShadowColor(). Voila, here is our dynamic - shadow.
- Because the character is a little bit too small for this scene, we make - it bigger using setScale(). And because the character is lighted by - a dynamic light, we need to normalize the normals to make the lighting - on it correct. This is always necessary if the scale of a dynamic lighted - model is not (1,1,1). Otherwise it would get too dark or too bright - because the normals will be scaled too.

- - - - -
	mesh = smgr->getMesh("../../media/dwarf.x");
-	scene::IAnimatedMeshSceneNode* anode = 0;
-
-	anode = smgr->addAnimatedMeshSceneNode(mesh);
-	anode->setPosition(core::vector3df(-50,20,-60));
-	anode->setAnimationSpeed(15);
-
-	// add shadow
-	anode->addShadowVolumeSceneNode();	
-	smgr->setShadowColor(video::SColor(220,0,0,0));
-
-	// make the model a little bit bigger and normalize its normals 
// because of this for correct lighting

anode->setScale(core::vector3df(2,2,2));
anode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true);

-

Finally we simply have to draw everything, that's all.

- - - - -
	scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();
-	camera->setPosition(core::vector3df(-50,50,-150));
-
-
-	int lastFPS = -1;
-
-	while(device->run())
-	{
-		driver->beginScene(true, true, 0);
-
-		smgr->drawAll();
-
-		driver->endScene();
-
-		int fps = driver->getFPS();
-
-		if (lastFPS != fps)
-		{
-		  core::stringw str = L"Irrlicht Engine - SpecialFX example [";
str += driver->getName();
str += "] FPS:";
str += fps;

device->setWindowCaption(str.c_str());
lastFPS = fps;
} - } - - device->drop(); - - return 0; -} - -
-
-

 

-

 

-

 

-
-
-

 

- - + + + + + + + +Tutorial 8: SpecialFX + + + + +
+ + + +
+
+
+
Tutorial 8: SpecialFX
+
+
+
+008shot.jpg +
+

This tutorial describes how to do special effects. It shows how to use stencil buffer shadows, the particle system, billboards, dynamic light, and the water surface scene node.

+

We start like in some tutorials before. Please note that this time, the 'shadows' flag in createDevice() is set to true, for we want to have a dynamic shadow cast from an animated character. If this example runs too slow, set it to false. The Irrlicht Engine also checks if your hardware doesn't support the stencil buffer, and then disables shadows by itself.

#include <irrlicht.h>
#include <iostream>
#include "driverChoice.h"
#include "exampleHelper.h"
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
int main()
{
// ask if user would like shadows
char i = 'y';
printf("Please press 'y' if you want to use realtime shadows.\n");
std::cin >> i;
const bool shadows = (i == 'y');
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;

Create device and exit if creation failed. We make the stencil flag optional to avoid slow screen modes for runs without shadows.

IrrlichtDevice *device =
createDevice(driverType, core::dimension2d<u32>(640, 480),
16, false, shadows);
if (device == 0)
return 1; // could not create selected driver.
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
const io::path mediaPath = getExampleMediaPath();

For our environment, we load a .3ds file. It is a small room I modeled with Anim8or and exported into the 3ds format because the Irrlicht Engine does not support the .an8 format. I am a very bad 3d graphic artist, and so the texture mapping is not very nice in this model. Luckily I am a better programmer than artist, and so the Irrlicht Engine is able to create a cool texture mapping for me: Just use the mesh manipulator and create a planar texture mapping for the mesh. If you want to see the mapping I made with Anim8or, uncomment this line. I also did not figure out how to set the material right in Anim8or, it has a specular light color which I don't really like. I'll switch it off too with this code.

scene::IAnimatedMesh* mesh = smgr->getMesh(mediaPath + "room.3ds");
smgr->getMeshManipulator()->makePlanarTextureMapping(mesh->getMesh(0), 0.004f);
scene::ISceneNode* node = 0;
node = smgr->addAnimatedMeshSceneNode(mesh);
node->setMaterialTexture(0, driver->getTexture(mediaPath + "wall.jpg"));
node->getMaterial(0).SpecularColor.set(0,0,0,0);

Now, for the first special effect: Animated water. It works like this: The WaterSurfaceSceneNode takes a mesh as input and makes it wave like a water surface. And if we let this scene node use a nice material like the EMT_REFLECTION_2_LAYER, it looks really cool. We are doing this with the next few lines of code. As input mesh, we create a hill plane mesh, without hills. But any other mesh could be used for this, you could even use the room.3ds (which would look really strange) if you want to.

mesh = smgr->addHillPlaneMesh( "myHill",
core::dimension2d<f32>(20,20),
core::dimension2d<u32>(40,40), 0, 0,
core::dimension2d<f32>(0,0),
core::dimension2d<f32>(10,10));
node = smgr->addWaterSurfaceSceneNode(mesh->getMesh(0), 3.0f, 300.0f, 30.0f);
node->setPosition(core::vector3df(0,7,0));
node->setMaterialTexture(0, driver->getTexture(mediaPath + "stones.jpg"));
node->setMaterialTexture(1, driver->getTexture(mediaPath + "water.jpg"));
node->setMaterialType(video::EMT_REFLECTION_2_LAYER);

The second special effect is very basic, I bet you saw it already in some Irrlicht Engine demos: A transparent billboard combined with a dynamic light. We simply create a light scene node, let it fly around, and to make it look more cool, we attach a billboard scene node to it.

// create light
scene::ILightSceneNode * lightNode = smgr->addLightSceneNode(0, core::vector3df(0,0,0),
video::SColorf(1.0f, 0.6f, 0.7f, 1.0f), 800.0f);
scene::ISceneNodeAnimator* anim = 0;
anim = smgr->createFlyCircleAnimator (core::vector3df(0,150,0),250.0f, 0.0005f);
lightNode ->addAnimator(anim);
anim->drop();
// attach billboard to light
node = smgr->addBillboardSceneNode(lightNode, core::dimension2d<f32>(50, 50));
node->setMaterialFlag(video::EMF_LIGHTING, false);
node->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
node->setMaterialTexture(0, driver->getTexture(mediaPath + "particlewhite.bmp"));

The next special effect is a lot more interesting: A particle system. The particle system in the Irrlicht Engine is quite modular and extensible, but yet easy to use. There is a particle system scene node into which you can put a particle emitter, which makes particles come out of nothing. These emitters are quite flexible and usually have lots of parameters like direction, amount, and color of the particles they create.

+

There are different emitters, for example a point emitter which lets particles pop out at a fixed point. If the particle emitters available in the engine are not enough for you, you can easily create your own ones, you'll simply have to create a class derived from the IParticleEmitter interface and attach it to the particle system using setEmitter(). In this example we create a box particle emitter, which creates particles randomly inside a box. The parameters define the box, direction of the particles, minimal and maximal new particles per second, color, and minimal and maximal lifetime of the particles.

+

Because only with emitters particle system would be a little bit boring, there are particle affectors which modify particles while they fly around. Affectors can be added to a particle system for simulating additional effects like gravity or wind. The particle affector we use in this example is an affector which modifies the color of the particles: It lets them fade out. Like the particle emitters, additional particle affectors can also be implemented by you, simply derive a class from IParticleAffector and add it with addAffector().

+

After we set a nice material to the particle system, we have a cool looking camp fire. By adjusting material, texture, particle emitter, and affector parameters, it is also easily possible to create smoke, rain, explosions, snow, and so on.

// create a particle system
scene::IParticleSystemSceneNode* ps =
smgr->addParticleSystemSceneNode(false);
if (ps)
{
scene::IParticleEmitter* em = ps->createBoxEmitter(
core::aabbox3d<f32>(-7,0,-7,7,1,7), // emitter size
core::vector3df(0.0f,0.06f,0.0f), // initial direction
80,100, // emit rate
video::SColor(0,255,255,255), // darkest color
video::SColor(0,255,255,255), // brightest color
800,2000,0, // min and max age, angle
core::dimension2df(10.f,10.f), // min size
core::dimension2df(20.f,20.f)); // max size
ps->setEmitter(em); // this grabs the emitter
em->drop(); // so we can drop it here without deleting it
scene::IParticleAffector* paf = ps->createFadeOutParticleAffector();
ps->addAffector(paf); // same goes for the affector
paf->drop();
ps->setPosition(core::vector3df(-70,60,40));
ps->setScale(core::vector3df(2,2,2));
ps->setMaterialFlag(video::EMF_LIGHTING, false);
ps->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
ps->setMaterialTexture(0, driver->getTexture(mediaPath + "fire.bmp"));
ps->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
}

Next we add a volumetric light node, which adds a glowing fake area light to the scene. Like with the billboards and particle systems we also assign a texture for the desired effect, though this time we'll use a texture animator to create the illusion of a magical glowing area effect.

scene::IVolumeLightSceneNode * n = smgr->addVolumeLightSceneNode(0, -1,
32, // Subdivisions on U axis
32, // Subdivisions on V axis
video::SColor(0, 255, 255, 255), // foot color
video::SColor(0, 0, 0, 0)); // tail color
if (n)
{
n->setScale(core::vector3df(56.0f, 56.0f, 56.0f));
n->setPosition(core::vector3df(-120,50,40));
// load textures for animation
core::array<video::ITexture*> textures;
for (s32 g=7; g > 0; --g)
{
core::stringc tmp(mediaPath);
tmp += "portal";
tmp += g;
tmp += ".bmp";
video::ITexture* t = driver->getTexture( tmp.c_str() );
textures.push_back(t);
}
// create texture animator
scene::ISceneNodeAnimator* glow = smgr->createTextureAnimator(textures, 150);
// add the animator
n->addAnimator(glow);
// drop the animator because it was created with a create() function
glow->drop();
}

As our last special effect, we want a dynamic shadow be cast from an animated character. For this we load a DirectX .x model and place it into our world. For creating the shadow, we simply need to call addShadowVolumeSceneNode(). The color of shadows is only adjustable globally for all shadows, by calling ISceneManager::setShadowColor(). Voila, here is our dynamic shadow.

+

Because the character is a little bit too small for this scene, we make it bigger using setScale(). And because the character is lighted by a dynamic light, we need to normalize the normals to make the lighting on it correct. This is always necessary if the scale of a dynamic lighted model is not (1,1,1). Otherwise it would get too dark or too bright because the normals will be scaled too.

// add animated character
mesh = smgr->getMesh(mediaPath + "dwarf.x");
scene::IAnimatedMeshSceneNode* anode = 0;
anode = smgr->addAnimatedMeshSceneNode(mesh);
anode->setPosition(core::vector3df(-50,20,-60));
anode->setAnimationSpeed(15);

Shadows still have to be drawn even then the node causing them is not visible itself. We have to disable culling if the node is animated or it's transformations change as otherwise the shadow is not updated correctly. If you have many objects and this becomes a speed problem you will have to figure out some manual culling (for exampling hiding all objects beyond a certain distance).

anode->setAutomaticCulling(scene::EAC_OFF);
// add shadow
anode->addShadowVolumeSceneNode();
smgr->setShadowColor(video::SColor(150,0,0,0));
// make the model a bit bigger
anode->setScale(core::vector3df(2,2,2));
// because of the scaling we have to normalize its normals for correct lighting
anode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true);
// let the dwarf slowly rotate around it's y axis
scene::ISceneNodeAnimator* ra = smgr->createRotationAnimator(irr::core::vector3df(0, 0.1f, 0));
anode->addAnimator(ra);
ra->drop();

Finally we simply have to draw everything, that's all.

scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();
camera->setPosition(core::vector3df(-50,50,-150));
camera->setFarValue(10000.0f); // this increase a shadow visible range.
// disable mouse cursor
device->getCursorControl()->setVisible(false);
s32 lastFPS = -1;
while(device->run())
if (device->isWindowActive())
{
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(0));
smgr->drawAll();
driver->endScene();
const s32 fps = driver->getFPS();
if (lastFPS != fps)
{
core::stringw str = L"Irrlicht Engine - SpecialFX example [";
str += driver->getName();
str += "] FPS:";
str += fps;
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}
device->drop();
return 0;
}
+ + +

 

+ + diff --git a/examples/09.Meshviewer/tutorial.html b/examples/09.Meshviewer/tutorial.html index d296b6ca..50d0d1be 100644 --- a/examples/09.Meshviewer/tutorial.html +++ b/examples/09.Meshviewer/tutorial.html @@ -1,182 +1,213 @@ - - -Irrlicht Engine Tutorial - - - - -
- - - - - - - - -
-
-
Tutorial 9. Mesh Viewer
-
-
-
-

This tutorial shows how to create a more complex application with - the engine. We construct a simple mesh viewer using the user interface - API and the scenemanagement of Irrlicht.
- The tutorial shows how to create and use Buttons, Windows, Toolbars, - Menus, ComboBoxes, Tabcontrols, Editboxes, Images, MessageBoxes, SkyBoxes, - and how to parse XML files with the integrated XML reader of the engine.

-

The program which is described here will look like this:

-


-

-
-
-
- - - - - - - -
Lets start!
-
-

We start like in most other tutorials: Include all nesessary header - files, add a comment to let the engine be linked with the right .lib - file in Visual Studio, and deklare some global variables. We also - add two 'using namespece' statements, so we do not need to write the - whole names of all classes. In this tutorial, we use a lot stuff from - the gui namespace.

- - - - -
#include <irrlicht.h>
#include <iostream>

using namespace irr;
using namespace gui;

#pragma comment(lib, "Irrlicht.lib")

IrrlichtDevice *Device = 0;
core::stringc StartUpModelFile;
core::stringw MessageText;
core::stringw Caption;
scene::IAnimatedMeshSceneNode* Model = 0;
scene::ISceneNode* SkyBox = 0;
-

The three following functions do several stuff used by the mesh - viewer. The first function showAboutText() simply displays a messagebox - with a caption and a message text. The texts will be stored in the - MessageText and Caption variables at startup.

- - - - -
void showAboutText()
{
// create modal message box with the text
// loaded from the xml file
.
Device->getGUIEnvironment()->addMessageBox(
Caption.c_str(), MessageText.c_str());
}
-

The second function loadModel() loads a model and displays it using - an addAnimatedMeshSceneNode and the scene manager. Nothing difficult. - It also displays a short message box, if the model could not be loaded. -

- - - - -
void loadModel(const c8* filename)
{
// load a model into the engine
if (Model)
Model->remove();
Model = 0;

scene::IAnimatedMesh* m = Device->getSceneManager()->getMesh(filename);
if (!m)
{
// model could not be loaded
if (StartUpModelFile != filename)
Device->getGUIEnvironment()->addMessageBox(
Caption.c_str(), L"The model could not be loaded. " \
L"Maybe it is not a supported file format.");
return;
}

// set default material properties
Model = Device->getSceneManager()->addAnimatedMeshSceneNode(m);
Model->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
Model->setMaterialFlag(video::EMF_LIGHTING, false);
Model->setDebugDataVisible(true);
}
-

Finally, the third function creates a toolbox window. In this simple - mesh viewer, this toolbox only contains a tab control with three edit - boxes for changing the scale of the displayed model.

- - - - -
void createToolBox()
{
// remove tool box if already there
IGUIEnvironment* env = Device->getGUIEnvironment();
IGUIElement* root = env->getRootGUIElement();
IGUIElement* e = root->getElementFromId(5000, true);
if (e) e->remove();

// create the toolbox window
IGUIWindow* wnd = env->addWindow(core::rect<s32>(450,25,640,480),
false, L"Toolset", 0, 5000);

// create tab control and tabs
IGUITabControl* tab = env->addTabControl(
core::rect<s32>(2,20,640-452,480-7), wnd, true, true);
IGUITab* t1 = tab->addTab(L"Scale");
IGUITab* t2 = tab->addTab(L"Empty Tab");

// add some edit boxes and a button to tab one
env->addEditBox(L"1.0", core::rect<s32>(40,50,130,70), true, t1, 901);
env->addEditBox(L"1.0", core::rect<s32>(40,80,130,100), true, t1, 902);
env->addEditBox(L"1.0", core::rect<s32>(40,110,130,130), true, t1, 903);
env->addButton(core::rect<s32>(10,150,100,190), t1, 1101, L"set");

// bring irrlicht engine logo to front, because it
// now may be below the newly created toolbox
root->bringToFront(root->getElementFromId(666, true));
}
-

To get all the events sent by the GUI Elements, we need to create - an event receiver. This one is really simple. If an event occurs, - it checks the id of the caller and the event type, and starts an action - based on these values. For example, if a menu item with id 100 was - selected, if opens a file-open-dialog.

-
- - - - -
class MyEventReceiver : public IEventReceiver
{
public:
virtual bool OnEvent(const SEvent& event)
{
if (event.EventType == EET_GUI_EVENT)
{
s32 id = event.GUIEvent.Caller->getID();
IGUIEnvironment* env = Device->getGUIEnvironment();
switch(event.GUIEvent.EventType)
{
case EGET_MENU_ITEM_SELECTED:
{
// a menu item was clicked
IGUIContextMenu* menu = (IGUIContextMenu*)event.GUIEvent.Caller;
s32 id = menu->getItemCommandId(menu->getSelectedItem());

switch(id)
{
case 100: // File -> Open Model
env->addFileOpenDialog(L"Please select a model file to open");
break;
case 200: // File -> Quit
Device->closeDevice();
break;
case 300: // View -> Skybox
SkyBox->setVisible(!SkyBox->isVisible());
break;
case 400: // View -> Debug Information
if (Model)
Model->setDebugDataVisible(!Model->isDebugDataVisible());
break;
case 500: // Help->About
showAboutText();
break;
case 610: // View -> Material -> Solid
if (Model)
Model->setMaterialType(video::EMT_SOLID);
break;
case 620: // View -> Material -> Transparent
if (Model)
Model->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
break;
case 630: // View -> Material -> Reflection
if (Model)
Model->setMaterialType(video::EMT_SPHERE_MAP);
break;
}
break;
}
case EGET_FILE_SELECTED:
{
// load the model file, selected in the file open dialog
IGUIFileOpenDialog* dialog =
(IGUIFileOpenDialog*)event.GUIEvent.Caller;
loadModel(core::stringc(dialog->getFilename()).c_str());
}
case EGET_BUTTON_CLICKED:
switch(id)
{
case 1101:
{
// set scale
gui::IGUIElement* root = env->getRootGUIElement();
core::vector3df scale;
core::stringc s;
s = root->getElementFromId(901, true)->getText();
scale.X = (f32)atof(s.c_str());
s = root->getElementFromId(902, true)->getText();
scale.Y = (f32)atof(s.c_str());
s = root->getElementFromId(903, true)->getText();
scale.Z = (f32)atof(s.c_str());
if (Model)
Model->setScale(scale);
}
break;
case 1102:
env->addFileOpenDialog(L"Please select a model file to open");
break;
case 1103:
showAboutText();
break;
case 1104:
createToolBox();
break;
}
break;
}
}
return false;
}
};
-

Most of the hard work is done. We only need to create the Irrlicht - Engine device and all the buttons, menus and toolbars. We start up the - engine as usual, using createDevice(). To make our application catch - events, we set our eventreceiver as parameter. The #ifdef WIN32 preprocessor - commands are not necesarry, but I included them to make the tutorial - use DirectX on Windows and OpenGL on all other platforms like Linux. - As you can see, there is also a unusual call to IrrlichtDevice::setResizeAble(). - This makes the render window resizeable, which is quite useful for a - mesh viewer.

- - - - -
int main()
{
// ask user for driver -
video::E_DRIVER_TYPE driverType; -
printf("Please select the driver you want for this example:\n"\
" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
" (d) Software Renderer\n (e) Apfelbaum Software Renderer\n"\
" (f) NullDevice\n (otherKey) exit\n\n");

char key;
std::cin >> key;

switch(key)
{
case 'a': driverType = video::EDT_DIRECT3D9;break;
case 'b': driverType = video::EDT_DIRECT3D8;break;
case 'c': driverType = video::EDT_OPENGL; break;
case 'd': driverType = video::EDT_SOFTWARE; break;
case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
case 'f': driverType = video::EDT_NULL; break;
default: return 1;
} -
// create device and exit if creation failed -
MyEventReceiver receiver;
Device = createDevice(driverType, core::dimension2d<s32>(640, 480),
16, false, false, false, &receiver); -
if (Device == 0)
return 1; // could not create selected driver.

Device->setResizable(true);
Device->setWindowCaption(L"Irrlicht Engine - Loading...");

video::IVideoDriver* driver = Device->getVideoDriver();
IGUIEnvironment* env = Device->getGUIEnvironment();
scene::ISceneManager* smgr = Device->getSceneManager();
-

The next step is to read the configuration file. It is stored in the - xml format and looks a little bit like this:
-
- <?xml version="1.0"?>
- <config>
- <startUpModel file="some filename" />
- <messageText caption="Irrlicht Engine Mesh Viewer">
- Hello!
- </messageText>
- </config>

-
- We need the data stored in there to be written into the global variables - StartUpModelFile, MessageText and Caption. This is now done using the - Irrlicht Engine integrated XML parser:

- - - - -
	// read configuration from xml file
io::IXMLReader* xml =
Device->getFileSystem()->createXMLReader("../../media/config.xml");
while(xml && xml->read())
{
switch(xml->getNodeType())
{
case io::EXN_TEXT:
// in this xml file, the only text which occurs is the messageText
MessageText = xml->getNodeData();
break;
case io::EXN_ELEMENT:
{
if (core::stringw("startUpModel") == xml->getNodeName())
StartUpModelFile = xml->getAttributeValue(L"file");
else
if (core::stringw("messageText") == xml->getNodeName())
Caption = xml->getAttributeValue(L"caption");
}
break;
}
}
if (xml)
xml->drop(); // don't forget to delete the xml reader
-
-

That wasn't difficult. Now we'll set a nicer font and create the Menu. - It is possible to create submenus for every menu item. The call menu->addItem(L"File", - -1, true, true); for example adds a new menu Item with the name "File" - and the id -1. The following parameter says that the menu item should - be enabled, and the last one says, that there should be a submenu. The - submenu can now be accessed with menu->getSubMenu(0), because the - "File" entry is the menu item with index 0.

- - - - -
	// set a nicer font
IGUISkin* skin = env->getSkin();
IGUIFont* font = env->getFont("../../media/fonthaettenschweiler.bmp");
if (font)
skin->setFont(font);

// create menu
gui::IGUIContextMenu* menu = env->addMenu();
menu->addItem(L"File", -1, true, true);
menu->addItem(L"View", -1, true, true);
menu->addItem(L"Help", -1, true, true);

gui::IGUIContextMenu* submenu;
submenu = menu->getSubMenu(0);
submenu->addItem(L"Open Model File...", 100);
submenu->addSeparator();
submenu->addItem(L"Quit", 200);

submenu = menu->getSubMenu(1);
submenu->addItem(L"toggle sky box visibility", 300);
submenu->addItem(L"toggle model debug information", 400);
submenu->addItem(L"model material", -1, true, true );

submenu = submenu->getSubMenu(2);
submenu->addItem(L"Solid", 610);
submenu->addItem(L"Transparent", 620);
submenu->addItem(L"Reflection", 630);

submenu = menu->getSubMenu(2);
submenu->addItem(L"About", 500); -
-
- We want a toolbar, onto which we can place colored buttons and important - looking stuff like a senseless combobox.
-
- - - - -
	// create toolbar
gui::IGUIToolBar* bar = env->addToolBar();
bar->addButton(1102, 0, driver->getTexture("../../media/open.bmp"));
bar->addButton(1103, 0, driver->getTexture("../../media/help.bmp"));
bar->addButton(1104, 0, driver->getTexture("../../media/tools.bmp"));

// create a combobox with some senseless texts
gui::IGUIComboBox* box = env->addComboBox(core::rect<s32>(100,5,200,25), bar);
box->addItem(L"Bilinear");
box->addItem(L"Trilinear");
box->addItem(L"Anisotropic");
box->addItem(L"Isotropic");
box->addItem(L"Psychedelic");
box->addItem(L"No filtering");
-
- To make the editor look a little bit better, we disable transparent gui - elements, and add a Irrlicht Engine logo. In addition, a text, which will - show the current frame per second value is created, and the window caption - changed.
-
- - - - -
	// disable alpha
for (s32 i=0; i<gui::EGDC_COUNT ; ++i)
{
video::SColor col = env->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i);
col.setAlpha(255);
env->getSkin()->setColor((gui::EGUI_DEFAULT_COLOR)i, col);
}

// add a tabcontrol
createToolBox();

// add the irrlicht engine logo
IGUIImage* img = env->addImage(core::rect<s32>(22,429,108,460), 0, 666);
img->setImage(driver->getTexture("../../media/irrlichtlogoaligned.jpg"));

// create fps text
IGUIStaticText* fpstext =
env->addStaticText(L"", core::rect<s32>(210,26,270,41), true);

// set window caption
Caption += " - [";
Caption += driver->getName();
Caption += "]";
Device->setWindowCaption(Caption.c_str());
-
- That's nearly the whole application. We simply show the about message - box at start up, and load the first model. To make everything look better, - a skybox is created and a user controled camera, to make the application - a little bit more interactive. Finally, everything is drawed in a standard - drawing loop.
-
- - - - -
	// show about message box and load default model
showAboutText();
loadModel(StartUpModelFile.c_str());

// add skybox

SkyBox = smgr->addSkyBoxSceneNode(
driver->getTexture("../../media/irrlicht2_up.bmp"),
driver->getTexture("../../media/irrlicht2_dn.bmp"),
driver->getTexture("../../media/irrlicht2_lf.bmp"),
driver->getTexture("../../media/irrlicht2_rt.bmp"),
driver->getTexture("../../media/irrlicht2_ft.bmp"),
driver->getTexture("../../media/irrlicht2_bk.bmp"));

// add a camera scene node
smgr->addCameraSceneNodeMaya();

// draw everything
while(Device->run() && driver)
if (Device->isWindowActive())
{
driver->beginScene(true, true, video::SColor(150,50,50,50));
smgr->drawAll();
env->drawAll();

driver->endScene();

core::stringw str = L"FPS: ";
str += driver->getFPS();
fpstext->setText(str.c_str());
}
Device->drop();
return 0;
}
-
- Compile and run this, and you have a fully functional 3d Mesh viewer.
-
-
-

 

- - + + + + + + + +Tutorial 9: Mesh Viewer + + + + +
+ + + +
+
+
+
Tutorial 9: Mesh Viewer
+
+
+
+009shot.jpg +
+

This tutorial show how to create a more complex application with the engine. We construct a simple mesh viewer using the user interface API and the scene management of Irrlicht. The tutorial show how to create and use Buttons, Windows, Toolbars, Menus, ComboBoxes, Tabcontrols, Editboxes, Images, MessageBoxes, SkyBoxes, and how to parse XML files with the integrated XML reader of the engine.

+

We start like in most other tutorials: Include all necessary header files, add a comment to let the engine be linked with the correct .lib file in Visual Studio, and declare some global variables. We also add two 'using namespace' statements, so we do not need to write the whole names of all classes. In this tutorial, we use a lot of stuff from the gui namespace.

#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
using namespace irr;
using namespace gui;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

Some global variables used later on

IrrlichtDevice *Device = 0;
io::path StartUpModelFile;
core::stringw MessageText;
core::stringw Caption;
scene::ISceneNode* Model = 0;
scene::ISceneNode* SkyBox = 0;
bool Octree=false;
bool UseLight=false;
scene::ICameraSceneNode* Camera[2] = {0, 0};
// Values used to identify individual GUI elements
enum
{
GUI_ID_DIALOG_ROOT_WINDOW = 0x10000,
GUI_ID_X_SCALE,
GUI_ID_Y_SCALE,
GUI_ID_Z_SCALE,
GUI_ID_OPEN_MODEL,
GUI_ID_SET_MODEL_ARCHIVE,
GUI_ID_LOAD_AS_OCTREE,
GUI_ID_SKY_BOX_VISIBLE,
GUI_ID_TOGGLE_DEBUG_INFO,
GUI_ID_DEBUG_OFF,
GUI_ID_DEBUG_BOUNDING_BOX,
GUI_ID_DEBUG_NORMALS,
GUI_ID_DEBUG_SKELETON,
GUI_ID_DEBUG_WIRE_OVERLAY,
GUI_ID_DEBUG_HALF_TRANSPARENT,
GUI_ID_DEBUG_BUFFERS_BOUNDING_BOXES,
GUI_ID_DEBUG_ALL,
GUI_ID_MODEL_MATERIAL_SOLID,
GUI_ID_MODEL_MATERIAL_TRANSPARENT,
GUI_ID_MODEL_MATERIAL_REFLECTION,
GUI_ID_CAMERA_MAYA,
GUI_ID_CAMERA_FIRST_PERSON,
GUI_ID_POSITION_TEXT,
GUI_ID_ABOUT,
GUI_ID_QUIT,
GUI_ID_TEXTUREFILTER,
GUI_ID_SKIN_TRANSPARENCY,
GUI_ID_SKIN_ANIMATION_FPS,
GUI_ID_BUTTON_SET_SCALE,
GUI_ID_BUTTON_SCALE_MUL10,
GUI_ID_BUTTON_SCALE_DIV10,
GUI_ID_BUTTON_OPEN_MODEL,
GUI_ID_BUTTON_SHOW_ABOUT,
GUI_ID_BUTTON_SHOW_TOOLBOX,
GUI_ID_BUTTON_SELECT_ARCHIVE,
GUI_ID_ANIMATION_INFO,
// And some magic numbers
MAX_FRAMERATE = 80,
DEFAULT_FRAMERATE = 30
};

Toggle between various cameras

void setActiveCamera(scene::ICameraSceneNode* newActive)
{
if (0 == Device)
return;
scene::ICameraSceneNode * active = Device->getSceneManager()->getActiveCamera();
active->setInputReceiverEnabled(false);
newActive->setInputReceiverEnabled(true);
Device->getSceneManager()->setActiveCamera(newActive);
}

Set the skin transparency by changing the alpha values of all skin-colors

void setSkinTransparency(s32 alpha, irr::gui::IGUISkin * skin)
{
for (s32 i=0; i<irr::gui::EGDC_COUNT ; ++i)
{
video::SColor col = skin->getColor((EGUI_DEFAULT_COLOR)i);
col.setAlpha(alpha);
skin->setColor((EGUI_DEFAULT_COLOR)i, col);
}
}

Update the display of the model scaling

void updateScaleInfo(scene::ISceneNode* model)
{
IGUIElement* toolboxWnd = Device->getGUIEnvironment()->getRootGUIElement()->getElementFromId(GUI_ID_DIALOG_ROOT_WINDOW, true);
if (!toolboxWnd)
return;
if (!model)
{
toolboxWnd->getElementFromId(GUI_ID_X_SCALE, true)->setText( L"-" );
toolboxWnd->getElementFromId(GUI_ID_Y_SCALE, true)->setText( L"-" );
toolboxWnd->getElementFromId(GUI_ID_Z_SCALE, true)->setText( L"-" );
}
else
{
core::vector3df scale = model->getScale();
toolboxWnd->getElementFromId(GUI_ID_X_SCALE, true)->setText( core::stringw(scale.X).c_str() );
toolboxWnd->getElementFromId(GUI_ID_Y_SCALE, true)->setText( core::stringw(scale.Y).c_str() );
toolboxWnd->getElementFromId(GUI_ID_Z_SCALE, true)->setText( core::stringw(scale.Z).c_str() );
}
}

Function showAboutText() displays a messagebox with a caption and a message text. The texts will be stored in the MessageText and Caption variables at startup.

void showAboutText()
{
// create modal message box with the text
// loaded from the xml file.
Device->getGUIEnvironment()->addMessageBox(
Caption.c_str(), MessageText.c_str());
}

Function loadModel() loads a model and displays it using an addAnimatedMeshSceneNode and the scene manager. Nothing difficult. It also displays a short message box, if the model could not be loaded.

void loadModel(const io::path& filename)
{
io::path extension;
core::getFileNameExtension(extension, filename);
extension.make_lower();
// if a texture is loaded apply it to the current model..
if (extension == ".jpg" || extension == ".pcx" ||
extension == ".png" || extension == ".ppm" ||
extension == ".pgm" || extension == ".pbm" ||
extension == ".psd" || extension == ".tga" ||
extension == ".bmp" || extension == ".wal" ||
extension == ".rgb" || extension == ".rgba")
{
// Ensure reloading texture by clearing old one out of cache
video::ITexture * texture = Device->getVideoDriver()->findTexture( filename );
if ( texture )
Device->getVideoDriver()->removeTexture(texture);
// Load the new one and put int on the model
texture = Device->getVideoDriver()->getTexture( filename );
if ( texture && Model )
{
Model->setMaterialTexture(0, texture);
}
return;
}
// if a archive is loaded add it to the FileArchive..
else if (extension == ".pk3" || extension == ".zip" || extension == ".pak" || extension == ".npk")
{
Device->getFileSystem()->addFileArchive(filename.c_str());
return;
}
// Remove old model
if (Model)
{
Model->remove();
Model = 0;
}
// .irr is a scene format, so load as scene and set Model pointer to first object in the scene
if (extension==".irr")
{
core::array<scene::ISceneNode*> outNodes;
Device->getSceneManager()->loadScene(filename);
Device->getSceneManager()->getSceneNodesFromType(scene::ESNT_ANIMATED_MESH, outNodes);
if (outNodes.size())
Model = outNodes[0];
return;
}
// load a model into the engine. Also log the time it takes to load it.
u32 then = Device->getTimer()->getRealTime();
scene::IAnimatedMesh* mesh = Device->getSceneManager()->getMesh( filename.c_str() );
Device->getLogger()->log("Loading time (ms): ", core::stringc(Device->getTimer()->getRealTime() - then).c_str());
if (!mesh)
{
// model could not be loaded
if (StartUpModelFile != filename)
Device->getGUIEnvironment()->addMessageBox(
Caption.c_str(), L"The model could not be loaded. " \
L"Maybe it is not a supported file format.");
return;
}
// set default material properties
if (Octree)
Model = Device->getSceneManager()->addOctreeSceneNode(mesh->getMesh(0));
else
{
scene::IAnimatedMeshSceneNode* animModel = Device->getSceneManager()->addAnimatedMeshSceneNode(mesh);
Model = animModel;
}
Model->setMaterialFlag(video::EMF_LIGHTING, UseLight);
Model->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, UseLight);
// Model->setMaterialFlag(video::EMF_BACK_FACE_CULLING, false);
Model->setDebugDataVisible(scene::EDS_OFF);
// we need to uncheck the menu entries. would be cool to fake a menu event, but
// that's not so simple. so we do it brute force
gui::IGUIContextMenu* menu = (gui::IGUIContextMenu*)Device->getGUIEnvironment()->getRootGUIElement()->getElementFromId(GUI_ID_TOGGLE_DEBUG_INFO, true);
if (menu)
for(int item = 1; item < 6; ++item)
menu->setItemChecked(item, false);
updateScaleInfo(Model);
}

Function createToolBox() creates a toolbox window. In this simple mesh viewer, this toolbox only contains a controls to change the scale and animation speed of the model and a control to set the transparency of the GUI-elements.

void createToolBox()
{
// remove tool box if already there
IGUIEnvironment* env = Device->getGUIEnvironment();
IGUIElement* root = env->getRootGUIElement();
IGUIElement* e = root->getElementFromId(GUI_ID_DIALOG_ROOT_WINDOW, true);
if (e)
e->remove();
// create the toolbox window
IGUIWindow* wnd = env->addWindow(core::rect<s32>(600,45,800,480),
false, L"Toolset", 0, GUI_ID_DIALOG_ROOT_WINDOW);
// create tab control and tabs
IGUITabControl* tab = env->addTabControl(
core::rect<s32>(2,20,800-602,480-7), wnd, true, true);
IGUITab* t1 = tab->addTab(L"Config");
// add some edit boxes and a button to tab one
env->addStaticText(L"Scale:",
core::rect<s32>(10,20,60,45), false, false, t1);
env->addStaticText(L"X:", core::rect<s32>(22,48,40,66), false, false, t1);
env->addEditBox(L"1.0", core::rect<s32>(40,46,130,66), true, t1, GUI_ID_X_SCALE);
env->addStaticText(L"Y:", core::rect<s32>(22,82,40,96), false, false, t1);
env->addEditBox(L"1.0", core::rect<s32>(40,76,130,96), true, t1, GUI_ID_Y_SCALE);
env->addStaticText(L"Z:", core::rect<s32>(22,108,40,126), false, false, t1);
env->addEditBox(L"1.0", core::rect<s32>(40,106,130,126), true, t1, GUI_ID_Z_SCALE);
env->addButton(core::rect<s32>(10,134,85,165), t1, GUI_ID_BUTTON_SET_SCALE, L"Set");
// quick scale buttons
env->addButton(core::rect<s32>(65,20,95,40), t1, GUI_ID_BUTTON_SCALE_MUL10, L"* 10");
env->addButton(core::rect<s32>(100,20,130,40), t1, GUI_ID_BUTTON_SCALE_DIV10, L"* 0.1");
updateScaleInfo(Model);
// add transparency control
env->addStaticText(L"GUI Transparency Control:",
core::rect<s32>(10,200,150,225), true, false, t1);
IGUIScrollBar* scrollbar = env->addScrollBar(true,
core::rect<s32>(10,225,150,240), t1, GUI_ID_SKIN_TRANSPARENCY);
scrollbar->setMax(255);
scrollbar->setPos(255);
// add framerate control
env->addStaticText(L":", core::rect<s32>(10,240,150,265), true, false, t1);
env->addStaticText(L"Framerate:",
core::rect<s32>(12,240,75,265), false, false, t1);
// current frame info
env->addStaticText(L"", core::rect<s32>(75,240,200,265), false, false, t1,
GUI_ID_ANIMATION_INFO);
scrollbar = env->addScrollBar(true,
core::rect<s32>(10,265,150,280), t1, GUI_ID_SKIN_ANIMATION_FPS);
scrollbar->setMax(MAX_FRAMERATE);
scrollbar->setMin(-MAX_FRAMERATE);
scrollbar->setPos(DEFAULT_FRAMERATE);
scrollbar->setSmallStep(1);
}

Function updateToolBox() is called each frame to update dynamic information in the toolbox.

void updateToolBox()
{
IGUIEnvironment* env = Device->getGUIEnvironment();
IGUIElement* root = env->getRootGUIElement();
IGUIElement* dlg = root->getElementFromId(GUI_ID_DIALOG_ROOT_WINDOW, true);
if (!dlg )
return;
// update the info we have about the animation of the model
IGUIStaticText * aniInfo = (IGUIStaticText *)(dlg->getElementFromId(GUI_ID_ANIMATION_INFO, true));
if (aniInfo)
{
if ( Model && scene::ESNT_ANIMATED_MESH == Model->getType() )
{
scene::IAnimatedMeshSceneNode* animatedModel = (scene::IAnimatedMeshSceneNode*)Model;
core::stringw str( (s32)core::round_(animatedModel->getAnimationSpeed()) );
str += L" Frame: ";
str += core::stringw((s32)animatedModel->getFrameNr());
aniInfo->setText(str.c_str());
}
else
aniInfo->setText(L"");
}
}
void onKillFocus()
{
// Avoid that the FPS-camera continues moving when the user presses alt-tab while
// moving the camera.
const core::list<scene::ISceneNodeAnimator*>& animators = Camera[1]->getAnimators();
core::list<irr::scene::ISceneNodeAnimator*>::ConstIterator iter = animators.begin();
while ( iter != animators.end() )
{
if ( (*iter)->getType() == scene::ESNAT_CAMERA_FPS )
{
// we send a key-down event for all keys used by this animator
scene::ISceneNodeAnimatorCameraFPS * fpsAnimator = static_cast<scene::ISceneNodeAnimatorCameraFPS*>(*iter);
const core::array<SKeyMap>& keyMap = fpsAnimator->getKeyMap();
for ( irr::u32 i=0; i< keyMap.size(); ++i )
{
irr::SEvent event;
event.EventType = EET_KEY_INPUT_EVENT;
event.KeyInput.Key = keyMap[i].KeyCode;
event.KeyInput.PressedDown = false;
fpsAnimator->OnEvent(event);
}
}
++iter;
}
}

Function hasModalDialog() checks if we currently have a modal dialog open.

bool hasModalDialog()
{
if ( !Device )
return false;
IGUIEnvironment* env = Device->getGUIEnvironment();
IGUIElement * focused = env->getFocus();
while ( focused )
{
if ( focused->isVisible() && focused->hasType(EGUIET_MODAL_SCREEN) )
return true;
focused = focused->getParent();
}
return false;
}

To get all the events sent by the GUI Elements, we need to create an event receiver. This one is really simple. If an event occurs, it checks the id of the caller and the event type, and starts an action based on these values. For example, if a menu item with id GUI_ID_OPEN_MODEL was selected, it opens a file-open-dialog.

class MyEventReceiver : public IEventReceiver
{
public:
virtual bool OnEvent(const SEvent& event)
{
// Key events
if (event.EventType == EET_KEY_INPUT_EVENT &&
event.KeyInput.PressedDown == false)
{
if ( OnKeyUp(event.KeyInput.Key) )
return true;
}
// GUI events
if (event.EventType == EET_GUI_EVENT)
{
s32 id = event.GUIEvent.Caller->getID();
IGUIEnvironment* env = Device->getGUIEnvironment();
switch(event.GUIEvent.EventType)
{
case EGET_MENU_ITEM_SELECTED:
// a menu item was clicked
OnMenuItemSelected( (IGUIContextMenu*)event.GUIEvent.Caller );
break;
case EGET_FILE_SELECTED:
{
// load the model file, selected in the file open dialog
IGUIFileOpenDialog* dialog =
(IGUIFileOpenDialog*)event.GUIEvent.Caller;
loadModel(dialog->getFileNameP());
}
break;
case EGET_SCROLL_BAR_CHANGED:
// control skin transparency
if (id == GUI_ID_SKIN_TRANSPARENCY)
{
const s32 pos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
setSkinTransparency(pos, env->getSkin());
}
// control animation speed
else if (id == GUI_ID_SKIN_ANIMATION_FPS)
{
const s32 pos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
if (scene::ESNT_ANIMATED_MESH == Model->getType())
((scene::IAnimatedMeshSceneNode*)Model)->setAnimationSpeed((f32)pos);
}
break;
case EGET_COMBO_BOX_CHANGED:
// control anti-aliasing/filtering
if (id == GUI_ID_TEXTUREFILTER)
{
OnTextureFilterSelected( (IGUIComboBox*)event.GUIEvent.Caller );
}
break;
case EGET_BUTTON_CLICKED:
switch(id)
{
case GUI_ID_BUTTON_SET_SCALE:
{
// set model scale
gui::IGUIElement* root = env->getRootGUIElement();
core::vector3df scale;
core::stringc s;
s = root->getElementFromId(GUI_ID_X_SCALE, true)->getText();
scale.X = (f32)atof(s.c_str());
s = root->getElementFromId(GUI_ID_Y_SCALE, true)->getText();
scale.Y = (f32)atof(s.c_str());
s = root->getElementFromId(GUI_ID_Z_SCALE, true)->getText();
scale.Z = (f32)atof(s.c_str());
if (Model)
Model->setScale(scale);
updateScaleInfo(Model);
}
break;
case GUI_ID_BUTTON_SCALE_MUL10:
if (Model)
Model->setScale(Model->getScale()*10.f);
updateScaleInfo(Model);
break;
case GUI_ID_BUTTON_SCALE_DIV10:
if (Model)
Model->setScale(Model->getScale()*0.1f);
updateScaleInfo(Model);
break;
case GUI_ID_BUTTON_OPEN_MODEL:
env->addFileOpenDialog(L"Please select a model file to open");
break;
case GUI_ID_BUTTON_SHOW_ABOUT:
showAboutText();
break;
case GUI_ID_BUTTON_SHOW_TOOLBOX:
createToolBox();
break;
case GUI_ID_BUTTON_SELECT_ARCHIVE:
env->addFileOpenDialog(L"Please select your game archive/directory");
break;
}
break;
default:
break;
}
}
return false;
}

Handle key-up events

bool OnKeyUp(irr::EKEY_CODE keyCode)
{
// Don't handle keys if we have a modal dialog open as it would lead
// to unexpected application behaviour for the user.
if ( hasModalDialog() )
return false;
// Escape swaps Camera Input
if (keyCode == irr::KEY_ESCAPE)
{
if (Device)
{
scene::ICameraSceneNode * camera =
Device->getSceneManager()->getActiveCamera();
if (camera)
{
camera->setInputReceiverEnabled( !camera->isInputReceiverEnabled() );
}
return true;
}
}
else if (keyCode == irr::KEY_F1)
{
// Swap display of position information about the camera
if (Device)
{
IGUIElement* elem = Device->getGUIEnvironment()->getRootGUIElement()->getElementFromId(GUI_ID_POSITION_TEXT);
if (elem)
elem->setVisible(!elem->isVisible());
}
}
else if (keyCode == irr::KEY_KEY_M)
{
if (Device)
Device->minimizeWindow();
}
else if (keyCode == irr::KEY_KEY_L)
{
UseLight=!UseLight;
if (Model)
{
Model->setMaterialFlag(video::EMF_LIGHTING, UseLight);
Model->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, UseLight);
}
}
return false;
}

Handle "menu item clicked" events.

void OnMenuItemSelected( IGUIContextMenu* menu )
{
s32 id = menu->getItemCommandId(menu->getSelectedItem());
IGUIEnvironment* env = Device->getGUIEnvironment();
switch(id)
{
case GUI_ID_OPEN_MODEL: // File -> Open Model File & Texture
env->addFileOpenDialog(L"Please select a model file to open");
break;
case GUI_ID_SET_MODEL_ARCHIVE: // File -> Set Model Archive
env->addFileOpenDialog(L"Please select your game archive/directory");
break;
case GUI_ID_LOAD_AS_OCTREE: // File -> LoadAsOctree
Octree = !Octree;
menu->setItemChecked(menu->getSelectedItem(), Octree);
break;
case GUI_ID_QUIT: // File -> Quit
Device->closeDevice();
break;
case GUI_ID_SKY_BOX_VISIBLE: // View -> Skybox
menu->setItemChecked(menu->getSelectedItem(), !menu->isItemChecked(menu->getSelectedItem()));
SkyBox->setVisible(!SkyBox->isVisible());
break;
case GUI_ID_DEBUG_OFF: // View -> Debug Information
menu->setItemChecked(menu->getSelectedItem()+1, false);
menu->setItemChecked(menu->getSelectedItem()+2, false);
menu->setItemChecked(menu->getSelectedItem()+3, false);
menu->setItemChecked(menu->getSelectedItem()+4, false);
menu->setItemChecked(menu->getSelectedItem()+5, false);
menu->setItemChecked(menu->getSelectedItem()+6, false);
if (Model)
Model->setDebugDataVisible(scene::EDS_OFF);
break;
case GUI_ID_DEBUG_BOUNDING_BOX: // View -> Debug Information
menu->setItemChecked(menu->getSelectedItem(), !menu->isItemChecked(menu->getSelectedItem()));
if (Model)
Model->setDebugDataVisible((scene::E_DEBUG_SCENE_TYPE)(Model->isDebugDataVisible()^scene::EDS_BBOX));
break;
case GUI_ID_DEBUG_NORMALS: // View -> Debug Information
menu->setItemChecked(menu->getSelectedItem(), !menu->isItemChecked(menu->getSelectedItem()));
if (Model)
Model->setDebugDataVisible((scene::E_DEBUG_SCENE_TYPE)(Model->isDebugDataVisible()^scene::EDS_NORMALS));
break;
case GUI_ID_DEBUG_SKELETON: // View -> Debug Information
menu->setItemChecked(menu->getSelectedItem(), !menu->isItemChecked(menu->getSelectedItem()));
if (Model)
Model->setDebugDataVisible((scene::E_DEBUG_SCENE_TYPE)(Model->isDebugDataVisible()^scene::EDS_SKELETON));
break;
case GUI_ID_DEBUG_WIRE_OVERLAY: // View -> Debug Information
menu->setItemChecked(menu->getSelectedItem(), !menu->isItemChecked(menu->getSelectedItem()));
if (Model)
Model->setDebugDataVisible((scene::E_DEBUG_SCENE_TYPE)(Model->isDebugDataVisible()^scene::EDS_MESH_WIRE_OVERLAY));
break;
case GUI_ID_DEBUG_HALF_TRANSPARENT: // View -> Debug Information
menu->setItemChecked(menu->getSelectedItem(), !menu->isItemChecked(menu->getSelectedItem()));
if (Model)
Model->setDebugDataVisible((scene::E_DEBUG_SCENE_TYPE)(Model->isDebugDataVisible()^scene::EDS_HALF_TRANSPARENCY));
break;
case GUI_ID_DEBUG_BUFFERS_BOUNDING_BOXES: // View -> Debug Information
menu->setItemChecked(menu->getSelectedItem(), !menu->isItemChecked(menu->getSelectedItem()));
if (Model)
Model->setDebugDataVisible((scene::E_DEBUG_SCENE_TYPE)(Model->isDebugDataVisible()^scene::EDS_BBOX_BUFFERS));
break;
case GUI_ID_DEBUG_ALL: // View -> Debug Information
menu->setItemChecked(menu->getSelectedItem()-1, true);
menu->setItemChecked(menu->getSelectedItem()-2, true);
menu->setItemChecked(menu->getSelectedItem()-3, true);
menu->setItemChecked(menu->getSelectedItem()-4, true);
menu->setItemChecked(menu->getSelectedItem()-5, true);
menu->setItemChecked(menu->getSelectedItem()-6, true);
if (Model)
Model->setDebugDataVisible(scene::EDS_FULL);
break;
case GUI_ID_ABOUT: // Help->About
showAboutText();
break;
case GUI_ID_MODEL_MATERIAL_SOLID: // View -> Material -> Solid
if (Model)
Model->setMaterialType(video::EMT_SOLID);
break;
case GUI_ID_MODEL_MATERIAL_TRANSPARENT: // View -> Material -> Transparent
if (Model)
Model->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
break;
case GUI_ID_MODEL_MATERIAL_REFLECTION: // View -> Material -> Reflection
if (Model)
Model->setMaterialType(video::EMT_SPHERE_MAP);
break;
case GUI_ID_CAMERA_MAYA:
setActiveCamera(Camera[0]);
break;
case GUI_ID_CAMERA_FIRST_PERSON:
setActiveCamera(Camera[1]);
break;
}
}

Handle the event that one of the texture-filters was selected in the corresponding combobox.

void OnTextureFilterSelected( IGUIComboBox* combo )
{
s32 pos = combo->getSelected();
switch (pos)
{
case 0:
if (Model)
{
Model->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
Model->setMaterialFlag(video::EMF_TRILINEAR_FILTER, false);
Model->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, false);
}
break;
case 1:
if (Model)
{
Model->setMaterialFlag(video::EMF_BILINEAR_FILTER, true);
Model->setMaterialFlag(video::EMF_TRILINEAR_FILTER, false);
}
break;
case 2:
if (Model)
{
Model->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
Model->setMaterialFlag(video::EMF_TRILINEAR_FILTER, true);
}
break;
case 3:
if (Model)
{
Model->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, true);
}
break;
case 4:
if (Model)
{
Model->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, false);
}
break;
}
}
};

Most of the hard work is done. We only need to create the Irrlicht Engine device and all the buttons, menus and toolbars. We start up the engine as usual, using createDevice(). To make our application catch events, we set our eventreceiver as parameter. As you can see, there is also a call to IrrlichtDevice::setResizeable(). This makes the render window resizeable, which is quite useful for a mesh viewer.

int main(int argc, char* argv[])
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// create device and exit if creation failed
MyEventReceiver receiver;
Device = createDevice(driverType, core::dimension2d<u32>(800, 600),
16, false, false, false, &receiver);
if (Device == 0)
return 1; // could not create selected driver.
Device->setResizable(true);
Device->setWindowCaption(L"Irrlicht Engine - Loading...");
video::IVideoDriver* driver = Device->getVideoDriver();
IGUIEnvironment* env = Device->getGUIEnvironment();
scene::ISceneManager* smgr = Device->getSceneManager();
smgr->getParameters()->setAttribute(scene::COLLADA_CREATE_SCENE_INSTANCES, true);
driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true);
smgr->addLightSceneNode(0, core::vector3df(200,200,200),
video::SColorf(1.0f,1.0f,1.0f),2000);
smgr->setAmbientLight(video::SColorf(0.3f,0.3f,0.3f));
// add our media directory as "search path"
Device->getFileSystem()->addFileArchive(getExampleMediaPath());

The next step is to read the configuration file. It is stored in the xml format and looks a little bit like this:

+
    <?xml version="1.0"?>
+    <config>
+        <startUpModel file="some filename" />
+        <messageText caption="Irrlicht Engine Mesh Viewer">
+            Hello!
+        </messageText>
+    </config>

We need the data stored in there to be written into the global variables StartUpModelFile, MessageText and Caption. This is now done using the Irrlicht Engine integrated XML parser:

// read configuration from xml file
io::IXMLReader* xml = Device->getFileSystem()->createXMLReader( L"config.xml");
while(xml && xml->read())
{
switch(xml->getNodeType())
{
case io::EXN_TEXT:
// in this xml file, the only text which occurs is the
// messageText
MessageText = xml->getNodeData();
break;
case io::EXN_ELEMENT:
{
if (core::stringw("startUpModel") == xml->getNodeName())
StartUpModelFile = xml->getAttributeValue(L"file");
else
if (core::stringw("messageText") == xml->getNodeName())
Caption = xml->getAttributeValue(L"caption");
}
break;
default:
break;
}
}
if (xml)
xml->drop(); // don't forget to delete the xml reader
// We can pass a model to load per command line parameter
if (argc > 1)
StartUpModelFile = argv[1];
// set a nicer font
IGUISkin* skin = env->getSkin();
IGUIFont* font = env->getFont("fonthaettenschweiler.bmp");
if (font)
skin->setFont(font);

Now create the Menu. It is possible to create submenus for every menu item. The call menu->addItem(L"File", -1, true, true); for example adds a new menu Item with the name "File" and the id -1. The following parameter says that the menu item should be enabled, and the last one says, that there should be a submenu. The submenu can now be accessed with menu->getSubMenu(0), because the "File" entry is the menu item with index 0.

gui::IGUIContextMenu* menu = env->addMenu();
menu->addItem(L"File", -1, true, true);
menu->addItem(L"View", -1, true, true);
menu->addItem(L"Camera", -1, true, true);
menu->addItem(L"Help", -1, true, true);
gui::IGUIContextMenu* submenu;
submenu = menu->getSubMenu(0);
submenu->addItem(L"Open Model File & Texture...", GUI_ID_OPEN_MODEL);
submenu->addItem(L"Set Model Archive...", GUI_ID_SET_MODEL_ARCHIVE);
submenu->addItem(L"Load as Octree", GUI_ID_LOAD_AS_OCTREE);
submenu->addSeparator();
submenu->addItem(L"Quit", GUI_ID_QUIT);
submenu = menu->getSubMenu(1);
submenu->addItem(L"sky box visible", GUI_ID_SKY_BOX_VISIBLE, true, false, true);
submenu->addItem(L"toggle model debug information", GUI_ID_TOGGLE_DEBUG_INFO, true, true);
submenu->addItem(L"model material", -1, true, true );
submenu = submenu->getSubMenu(1);
submenu->addItem(L"Off", GUI_ID_DEBUG_OFF);
submenu->addItem(L"Bounding Box", GUI_ID_DEBUG_BOUNDING_BOX);
submenu->addItem(L"Normals", GUI_ID_DEBUG_NORMALS);
submenu->addItem(L"Skeleton", GUI_ID_DEBUG_SKELETON);
submenu->addItem(L"Wire overlay", GUI_ID_DEBUG_WIRE_OVERLAY);
submenu->addItem(L"Half-Transparent", GUI_ID_DEBUG_HALF_TRANSPARENT);
submenu->addItem(L"Buffers bounding boxes", GUI_ID_DEBUG_BUFFERS_BOUNDING_BOXES);
submenu->addItem(L"All", GUI_ID_DEBUG_ALL);
submenu = menu->getSubMenu(1)->getSubMenu(2);
submenu->addItem(L"Solid", GUI_ID_MODEL_MATERIAL_SOLID);
submenu->addItem(L"Transparent", GUI_ID_MODEL_MATERIAL_TRANSPARENT);
submenu->addItem(L"Reflection", GUI_ID_MODEL_MATERIAL_REFLECTION);
submenu = menu->getSubMenu(2);
submenu->addItem(L"Maya Style", GUI_ID_CAMERA_MAYA);
submenu->addItem(L"First Person", GUI_ID_CAMERA_FIRST_PERSON);
submenu = menu->getSubMenu(3);
submenu->addItem(L"About", GUI_ID_ABOUT);

Below the menu we want a toolbar, onto which we can place colored buttons and important looking stuff like a senseless combobox.

// create toolbar
gui::IGUIToolBar* bar = env->addToolBar();
video::ITexture* image = driver->getTexture("open.png");
bar->addButton(GUI_ID_BUTTON_OPEN_MODEL, 0, L"Open a model",image, 0, false, true);
image = driver->getTexture("tools.png");
bar->addButton(GUI_ID_BUTTON_SHOW_TOOLBOX, 0, L"Open Toolset",image, 0, false, true);
image = driver->getTexture("zip.png");
bar->addButton(GUI_ID_BUTTON_SELECT_ARCHIVE, 0, L"Set Model Archive",image, 0, false, true);
image = driver->getTexture("help.png");
bar->addButton(GUI_ID_BUTTON_SHOW_ABOUT, 0, L"Open Help", image, 0, false, true);
// create a combobox for texture filters
gui::IGUIComboBox* box = env->addComboBox(core::rect<s32>(250,4,350,23), bar, GUI_ID_TEXTUREFILTER);
box->addItem(L"No filtering");
box->addItem(L"Bilinear");
box->addItem(L"Trilinear");
box->addItem(L"Anisotropic");
box->addItem(L"Isotropic");

To make the editor look a little bit better, we disable transparent gui elements, and add an Irrlicht Engine logo. In addition, a text showing the current frames per second value is created and the window caption is changed.

// disable alpha
for (s32 i=0; i<gui::EGDC_COUNT ; ++i)
{
video::SColor col = env->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i);
col.setAlpha(255);
env->getSkin()->setColor((gui::EGUI_DEFAULT_COLOR)i, col);
}
// add a tabcontrol
createToolBox();
// create fps text
IGUIStaticText* fpstext = env->addStaticText(L"",
core::rect<s32>(400,4,570,23), true, false, bar);
IGUIStaticText* postext = env->addStaticText(L"",
core::rect<s32>(10,50,470,80),false, false, 0, GUI_ID_POSITION_TEXT);
postext->setVisible(false);
// set window caption
Caption += " - [";
Caption += driver->getName();
Caption += "]";
Device->setWindowCaption(Caption.c_str());

Now we show the about message box at start up, and load the first model. To make everything look better a skybox is created. We also add a user controlled camera, to make the application more interactive. Finally, everything is drawn in a standard drawing loop.

// show about message box and load default model
if (argc==1)
showAboutText();
loadModel(StartUpModelFile.c_str());
// add skybox
SkyBox = smgr->addSkyBoxSceneNode(
driver->getTexture("irrlicht2_up.jpg"),
driver->getTexture("irrlicht2_dn.jpg"),
driver->getTexture("irrlicht2_lf.jpg"),
driver->getTexture("irrlicht2_rt.jpg"),
driver->getTexture("irrlicht2_ft.jpg"),
driver->getTexture("irrlicht2_bk.jpg"));
// add a camera scene node
Camera[0] = smgr->addCameraSceneNodeMaya();
Camera[0]->setFarValue(20000.f);
// Maya cameras reposition themselves relative to their target, so target the location
// where the mesh scene node is placed.
Camera[0]->setTarget(core::vector3df(0,30,0));
Camera[1] = smgr->addCameraSceneNodeFPS();
Camera[1]->setFarValue(20000.f);
Camera[1]->setPosition(core::vector3df(0,0,-70));
Camera[1]->setTarget(core::vector3df(0,30,0));
setActiveCamera(Camera[0]);
// load the irrlicht engine logo
IGUIImage *img =
env->addImage(driver->getTexture("irrlichtlogo3.png"),
core::position2d<s32>(10, driver->getScreenSize().Height - 128));
// lock the logo's edges to the bottom left corner of the screen
img->setAlignment(EGUIA_UPPERLEFT, EGUIA_UPPERLEFT,
EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT);
// remember state so we notice when the window does lose the focus
bool hasFocus = Device->isWindowFocused();
// draw everything
while(Device->run() && driver)
{
// Catch focus changes (workaround until Irrlicht has events for this)
bool focused = Device->isWindowFocused();
if ( hasFocus && !focused )
onKillFocus();
hasFocus = focused;
if (Device->isWindowActive())
{
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(150,50,50,50));
smgr->drawAll();
env->drawAll();
driver->endScene();
// update information about current frame-rate
core::stringw str(L"FPS: ");
str.append(core::stringw(driver->getFPS()));
str += L" Tris: ";
str.append(core::stringw(driver->getPrimitiveCountDrawn()));
fpstext->setText(str.c_str());
// update information about the active camera
scene::ICameraSceneNode* cam = Device->getSceneManager()->getActiveCamera();
str = L"Pos: ";
str.append(core::stringw(cam->getPosition().X));
str += L" ";
str.append(core::stringw(cam->getPosition().Y));
str += L" ";
str.append(core::stringw(cam->getPosition().Z));
str += L" Tgt: ";
str.append(core::stringw(cam->getTarget().X));
str += L" ";
str.append(core::stringw(cam->getTarget().Y));
str += L" ";
str.append(core::stringw(cam->getTarget().Z));
postext->setText(str.c_str());
// update the tool dialog
updateToolBox();
}
else
Device->yield();
}
Device->drop();
return 0;
}
+ + +

 

+ + diff --git a/examples/10.Shaders/tutorial.html b/examples/10.Shaders/tutorial.html index 05c4f08a..7b63cea5 100644 --- a/examples/10.Shaders/tutorial.html +++ b/examples/10.Shaders/tutorial.html @@ -1,566 +1,210 @@ - - -Irrlicht Engine Tutorial - - - - -
- - - - - - - - -
-
-
Tutorial 10. Shaders
-
-
-
-

This tutorial shows how to use shaders for D3D8, D3D9 and OpenGL with - the engine and how to create new material types with them. It also shows - how to disable the generation of mipmaps at texture loading, and how - to use text scene nodes.

-

This tutorial does not explain how shaders work. I would recommend - to read the D3D or OpenGL documentation, to search a tutorial, or to - read a book about this.

-

The program which is described here will look like this:

-


-

-
-
-
- - - - - - - -
Lets start!
-
-

At first, we need to include all headers and do the stuff we always - do, like in nearly all other tutorials:

- - - - -
#include <irrlicht.h>
#include <iostream>

using namespace irr;

#pragma comment(lib, "Irrlicht.lib")
-

Because we want to use some interesting shaders in this tutorials, - we need to set some data for them to make them able to compute nice - colors. In this example, we'll use a simple vertex shader which will - calculate the color of the vertex based on the position of the camera. - For this, the shader needs the following data: The inverted world - matrix for transforming the normal, the clip matrix for transforming - the position, the camera position and the world position of the object - for the calculation of the angle of light, and the color of the light. - To be able to tell the shader all this data every frame, we have to - derive a class from the IShaderConstantSetCallBack interface and override - its only method, namely OnSetConstants(). This method will be called - every time the material is set.
- The method setVertexShaderConstant() of the IMaterialRendererServices - interface is used to set the data the shader needs. If the user chose - to use a High Level shader language like HLSL instead of Assembler - in this example, you have to set the variable name as parameter instead - of the register index.

- - - - -
IrrlichtDevice* device = 0;
bool UseHighLevelShaders = false;

class MyShaderCallBack : public video::IShaderConstantSetCallBack
{
public: -
virtual void OnSetConstants(video::IMaterialRendererServices* services, s32 userData)
{
video::IVideoDriver* driver = services->getVideoDriver();

// set inverted world matrix
// if we are using highlevel shaders (the user can select this when
// starting the program), we must set the constants by name.

core::matrix4 invWorld = driver->getTransform(video::ETS_WORLD);
invWorld.makeInverse();

if (UseHighLevelShaders)
services->setVertexShaderConstant("mInvWorld", &invWorld.M[0], 16);
else
services->setVertexShaderConstant(&invWorld.M[0], 0, 4);

// set clip matrix
core::matrix4 worldViewProj;
worldViewProj = driver->getTransform(video::ETS_PROJECTION);
worldViewProj *= driver->getTransform(video::ETS_VIEW);
worldViewProj *= driver->getTransform(video::ETS_WORLD);

if (UseHighLevelShaders)
services->setVertexShaderConstant("mWorldViewProj", &worldViewProj.M[0], 16);
else
services->setVertexShaderConstant(&worldViewProj.M[0], 4, 4);

// set camera position
core::vector3df pos = device->getSceneManager()->
getActiveCamera()->getAbsolutePosition();

if (UseHighLevelShaders)
services->setVertexShaderConstant("mLightPos", reinterpret_cast<f32*>(&pos), 3);
else
services->setVertexShaderConstant(reinterpret_cast<f32*>(&pos), 8, 1);

// set light color
video::SColorf col(0.0f,1.0f,1.0f,0.0f);

if (UseHighLevelShaders)
services->setVertexShaderConstant("mLightColor", reinterpret_cast<f32*>(&col), 4);
else
services->setVertexShaderConstant(reinterpret_cast<f32*>(&col), 9, 1);

// set transposed world matrix
core::matrix4 world = driver->getTransform(video::ETS_WORLD);
world = world.getTransposed();

if (UseHighLevelShaders)
services->setVertexShaderConstant("mTransWorld", &world.M[0], 16);
else
services->setVertexShaderConstant(&world.M[0], 10, 4);
}
};
-

The next few lines start up the engine. Just like in most other - tutorials before. But in addition, we ask the user if he wants this - example to use high level shaders if he selected a driver which is - capable of doing so.

- - - - -
int main()
{
// let user select driver type

video::E_DRIVER_TYPE driverType = video::EDT_DIRECTX9;

printf("Please select the driver you want for this example:\n"\
" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
" (d) Software Renderer\n (e) Apfelbaum Software Renderer\n"\
" (f) NullDevice\n (otherKey) exit\n\n");

char i;
std::cin >> i;

switch(i)
{
case 'a': driverType = video::EDT_DIRECT3D9;break;
case 'b': driverType = video::EDT_DIRECT3D8;break;
case 'c': driverType = video::EDT_OPENGL; break;
case 'd': driverType = video::EDT_SOFTWARE; break;
case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
case 'f': driverType = video::EDT_NULL; break;
default: return 1;
}

// ask the user if we should use high level shaders for this example
if (driverType == video::EDT_DIRECT3D9 ||
driverType == video::EDT_OPENGL) - {
printf("Please press 'y' if you want to use high level shaders.\n");
std::cin >> i;
if (i == 'y')
UseHighLevelShaders = true;
}

// create device

device = createDevice(driverType, core::dimension2d<s32>(640, 480));

if (device == 0)
{
printf("\nWas not able to create driver.\n"\
"Please restart and select another driver.\n"
);
getch();
return 1;
}

video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* gui = device->getGUIEnvironment();
-

Now for the more interesting parts. If we are using Direct3D, we - want to load vertex and pixel shader programs, if we have
- OpenGL, we want to use ARB fragment and vertex programs. I wrote the - corresponding programs down into the files d3d8.ps, d3d8.vs, d3d9.ps, - d3d9.vs, opengl.ps and opengl.vs. We only need the right filenames - now. This is done in the following switch. Note, that it is not necessary - to write the shaders into text files, like in this example. You can - even write the shaders directly as strings into the cpp source file, - and use later addShaderMaterial() instead of addShaderMaterialFromFiles().

- - - - -
	c8* vsFileName = 0; // filename for the vertex shader
c8* psFileName = 0; // filename for the pixel shader

switch(driverType)
{
case video::EDT_DIRECT3D8:
psFileName = "../../media/d3d8.psh";
vsFileName = "../../media/d3d8.vsh";
break;
case video::EDT_DIRECT3D9:
if (UseHighLevelShaders)
{
psFileName = "../../media/d3d9.hlsl";
vsFileName = psFileName; // both shaders are in the same file
}
else
{
psFileName = "../../media/d3d9.psh";
vsFileName = "../../media/d3d9.vsh";
}
break;
case video::EDT_OPENGL:
if (UseHighLevelShaders)
{
psFileName = "../../media/opengl.frag";
vsFileName = "../../media/opengl.vert";
}
else
{
psFileName = "../../media/opengl.psh";
vsFileName = "../../media/opengl.vsh";
}
break;
}
-
-

In addition, we check if the hardware and the selected renderer - is capable of executing the shaders we want. If not, we simply set - the filename string to 0. This is not necessary, but useful in this - example: For example, if the hardware is able to execute vertex shaders - but not pixel shaders, we create a new material which only uses the - vertex shader, and no pixel shader. Otherwise, if we would tell the - engine to create this material and the engine sees that the hardware - wouldn't be able to fullfill the request completely,
- it would not create any new material at all. So in this example you - would see at least the vertex shader in action, without the pixel - shader.

-
- - - - -
	if (!driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1) &&
!driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1))
{
device->getLogger()->log("WARNING: Pixel shaders disabled "\
"because of missing driver/hardware support.");
psFileName = 0;
}

if (!driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1) &&
!driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1))
{
device->getLogger()->log("WARNING: Vertex shaders disabled "\
"because of missing driver/hardware support.");
vsFileName = 0;
}
-

Now lets create the new materials.
- As you maybe know from previous examples, a material type in the Irrlicht - engine is set by simply changing the MaterialType value in the SMaterial - struct. And this value is just a simple 32 bit value, like video::EMT_SOLID. - So we only need the engine to create a new value for us which we can - set there. To do this, we get a pointer to the IGPUProgrammingServices - and call addShaderMaterialFromFiles(), which returns such a new 32 bit - value. That's all.
- The parameters to this method are the following: First, the names of - the files containing the code of the vertex and the pixel shader.
- If you would use addShaderMaterial() instead, you would not need file - names, then you could write the code of the shader directly as string. - The following parameter is a pointer to the IShaderConstantSetCallBack - class we wrote at the beginning of this tutorial. If you don't want - to set constants, set this to 0. The last paramter tells the engine - which material it should use as base material.
- To demonstrate this, we create two materials with a different base material, - one with EMT_SOLID and one with EMT_TRANSPARENT_ADD_COLOR.

- - - - -
	// create materials

video::IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();

s32 newMaterialType1 = 0;
s32 newMaterialType2 = 0;

if (gpu)
{
MyShaderCallBack* mc = new MyShaderCallBack();
- // create the shaders depending on if the user wanted high level
// or low level shaders:


if (UseHighLevelShaders)
{
// create material from high level shaders (hlsl or glsl)

newMaterialType1 = gpu->addHighLevelShaderMaterialFromFiles(
vsFileName, "vertexMain", video::EVST_VS_1_1,
psFileName, "pixelMain", video::EPST_PS_1_1,
mc, video::EMT_SOLID);

newMaterialType2 = gpu->addHighLevelShaderMaterialFromFiles(
vsFileName, "vertexMain", video::EVST_VS_1_1,
psFileName, "pixelMain", video::EPST_PS_1_1,
mc, video::EMT_TRANSPARENT_ADD_COLOR);
}
else
{
// create material from low level shaders (asm or arb_asm)

newMaterialType1 = gpu->addShaderMaterialFromFiles(vsFileName,
psFileName, mc, video::EMT_SOLID);

newMaterialType2 = gpu->addShaderMaterialFromFiles(vsFileName,
psFileName, mc, video::EMT_TRANSPARENT_ADD_COLOR);
}

mc->drop();
}
-

Now its time for testing out the materials. We create a test cube - and set the material we created. In addition, we add a text scene node - to the cube and a rotatation animator, to make it look more interesting - and important.

- - - - -

-	// create test scene node 1, with the new created material type 1
-
-	scene::ISceneNode* node = smgr->addCubeSceneNode(50);
-	node->setPosition(core::vector3df(0,0,0));
-	node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
-	node->setMaterialFlag(video::EMF_LIGHTING, false);
-	node->setMaterialType((video::E_MATERIAL_TYPE)newMaterialType1);
-
-	smgr->addTextSceneNode(gui->getBuiltInFont(),
-			L"PS & VS & EMT_SOLID",
-			video::SColor(255,255,255,255),	node);
-
-	scene::ISceneNodeAnimator* anim = smgr->createRotationAnimator(
-			core::vector3df(0,0.3f,0));
-	node->addAnimator(anim);
-	anim->drop();
-

Same for the second cube, but with the second material we created.

- - - - -
	// create test scene node 2, with the new created material type 2
-
-	node = smgr->addCubeSceneNode(50);
-	node->setPosition(core::vector3df(0,-10,50));
-	node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
-	node->setMaterialFlag(video::EMF_LIGHTING, false);
-	node->setMaterialType((video::E_MATERIAL_TYPE)newMaterialType2);
-
-	smgr->addTextSceneNode(gui->getBuiltInFont(),
-			L"PS & VS & EMT_TRANSPARENT",
-			video::SColor(255,255,255,255),	node);
-
-	anim = smgr->createRotationAnimator(core::vector3df(0,0.3f,0));
-	node->addAnimator(anim);
-	anim->drop();
-
- Then we add a third cube without a shader on it, to be able to compare - the cubes.
-
- - - - -
	// add a scene node with no shader 
-
-	node = smgr->addCubeSceneNode(50);
-	node->setPosition(core::vector3df(0,50,25));
-	node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
-	node->setMaterialFlag(video::EMF_LIGHTING, false);
-	smgr->addTextSceneNode(gui->getBuiltInFont(), L"NO SHADER",
-		video::SColor(255,255,255,255), node);
-            
-
- And last, we add a skybox and a user controlled camera to the scene. For - the skybox textures, we disable mipmap generation, because we don't need - mipmaps on it.
-
- - - - -
	// add a nice skybox

driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);

smgr->addSkyBoxSceneNode(
driver->getTexture("../../media/irrlicht2_up.jpg"),
driver->getTexture("../../media/irrlicht2_dn.jpg"),
driver->getTexture("../../media/irrlicht2_lf.jpg"),
driver->getTexture("../../media/irrlicht2_rt.jpg"),
driver->getTexture("../../media/irrlicht2_ft.jpg"),
driver->getTexture("../../media/irrlicht2_bk.jpg"));

driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, true);

// add a camera and disable the mouse cursor

scene::ICameraSceneNode* cam = smgr->addCameraSceneNodeFPS(0, 100.0f, 100.0f);
cam->setPosition(core::vector3df(-100,50,100));
cam->setTarget(core::vector3df(0,0,0));
device->getCursorControl()->setVisible(false);
-
- Now draw everything. That's all.
-
- - - - -
	int lastFPS = -1;

while(device->run())
if (device->isWindowActive())
{
driver->beginScene(true, true, video::SColor(255,0,0,0));
smgr->drawAll();
driver->endScene();

int fps = driver->getFPS();

if (lastFPS != fps)
{
core::stringw str = L"Irrlicht Engine - Vertex and pixel shader example [";
str += driver->getName();
str += "] FPS:";
str += fps;
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}

device->drop();

return 0;
-
- Compile and run this, and I hope you have fun with your new little shader - writing tool :).
-
-
-
- - - - - - - -
Shader files
-
-

The files containing the shaders can be found in the media directory - of the SDK. However, they look like this:

- - - - - - - -
D3D9.HLSL
-
-// part of the Irrlicht Engine Shader example.
-// These simple Direct3D9 pixel and vertex shaders will be loaded by the shaders
-// example. Please note that these example shaders don't do anything really useful. 
-// They only demonstrate that shaders can be used in Irrlicht.
-
-//-----------------------------------------------------------------------------
-// Global variables
-//-----------------------------------------------------------------------------
-float4x4 mWorldViewProj;  // World * View * Projection transformation
-float4x4 mInvWorld;       // Inverted world matrix
-float4x4 mTransWorld;     // Transposed world matrix
-float3 mLightPos;         // Light position
-float4 mLightColor;       // Light color
-
-
-// Vertex shader output structure
-struct VS_OUTPUT
-{
-	float4 Position   : POSITION;   // vertex position 
-	float4 Diffuse    : COLOR0;     // vertex diffuse color
-	float2 TexCoord   : TEXCOORD0;  // tex coords
-};
-
-
-VS_OUTPUT vertexMain( in float4 vPosition : POSITION,
-                      in float3 vNormal   : NORMAL,
-                      float2 texCoord     : TEXCOORD0 )
-{
-	VS_OUTPUT Output;
-
-	// transform position to clip space 
-	Output.Position = mul(vPosition, mWorldViewProj);
-	
-	// transform normal 
-	float3 normal = mul(vNormal, mInvWorld);
-	
-	// renormalize normal 
-	normal = normalize(normal);
-	
-	// position in world coodinates
-	float3 worldpos = mul(mTransWorld, vPosition);
-	
-	// calculate light vector, vtxpos - lightpos
-	float3 lightVector = worldpos - mLightPos;
-	
-	// normalize light vector 
-	lightVector = normalize(lightVector);
-	
-	// calculate light color 
-	float3 tmp = dot(-lightVector, normal);
-	tmp = lit(tmp.x, tmp.y, 1.0);
-	
-	tmp = mLightColor * tmp.y;
-	Output.Diffuse = float4(tmp.x, tmp.y, tmp.z, 0);
-	Output.TexCoord = texCoord;
-	
-	return Output;
-}
-
-
-
-// Pixel shader output structure
-struct PS_OUTPUT
-{
-    float4 RGBColor : COLOR0;  // Pixel color    
-};
-
-
-sampler2D tex0;
-	
-PS_OUTPUT pixelMain( float2 TexCoord : TEXCOORD0,
-                     float4 Position : POSITION,
-                     float4 Diffuse  : COLOR0 ) 
-{ 
-	PS_OUTPUT Output;
-
-	float4 col = tex2D( tex0, TexCoord );  // sample color map
-	
-	// multiply with diffuse and do other senseless operations
-	Output.RGBColor = Diffuse * col;
-	Output.RGBColor *= 4.0;
-
-	return Output;
-}
-
- - - - - - - -
D3D9.VSH
-; part of the Irrlicht Engine Shader example.
-; This Direct3D9 vertex shader will be loaded by the engine.
-; Please note that these example shaders don't do anything really useful. 
-; They only demonstrate that shaders can be used in Irrlicht.
-vs.1.1 - -dcl_position v0; ; declare position -dcl_normal v1; ; declare normal -dcl_color v2; ; declare color -dcl_texcoord0 v3; ; declare texture coordinate
-; transpose and transform position to clip space -mul r0, v0.x, c4 -mad r0, v0.y, c5, r0 -mad r0, v0.z, c6, r0 -add oPos, c7, r0 - -; transform normal -dp3 r1.x, v1, c0 -dp3 r1.y, v1, c1 -dp3 r1.z, v1, c2 - -; renormalize normal -dp3 r1.w, r1, r1 -rsq r1.w, r1.w -mul r1, r1, r1.w - -; calculate light vector -m4x4 r6, v0, c10 ; vertex into world position -add r2, c8, -r6 ; vtxpos - lightpos - -; normalize light vector -dp3 r2.w, r2, r2 -rsq r2.w, r2.w -mul r2, r2, r2.w - -; calculate light color -dp3 r3, r1, r2 ; dp3 with negative light vector -lit r5, r3 ; clamp to zero if r3 < 0, r5 has diffuce component in r5.y -mul oD0, r5.y, c9 ; ouput diffuse color -mov oT0, v3 ; store texture coordinates
-
- - - - - - - -
D3D9.PSH
-; part of the Irrlicht Engine Shader example.
-; This simple Direct3D9 pixel shader will be loaded by the engine.
-; Please note that these example shaders don't do anything really useful. 
-; They only demonstrate that shaders can be used in Irrlicht.
-ps.1.1 - -tex t0 ; sample color map -add r0, v0, v0 ; mulitply with color -mul t0, t0, r0 ; mulitply with color -add r0, t0, t0 ; make it brighter and store result -
-
- - - - - - - -
D3D8.VSH
-; part of the Irrlicht Engine Shader example.
-; This Direct3D9 vertex shader will be loaded by the engine.
-; Please note that these example shaders don't do anything really useful. 
-; They only demonstrate that shaders can be used in Irrlicht.
-vs.1.1 - -; transpose and transform position to clip space -mul r0, v0.x, c4 -mad r0, v0.y, c5, r0 -mad r0, v0.z, c6, r0 -add oPos, c7, r0 - -; transform normal -dp3 r1.x, v1, c0 -dp3 r1.y, v1, c1 -dp3 r1.z, v1, c2 - -; renormalize normal -dp3 r1.w, r1, r1 -rsq r1.w, r1.w -mul r1, r1, r1.w - -; calculate light vector -m4x4 r6, v0, c10 ; vertex into world position -add r2, c8, -r6 ; vtxpos - lightpos - -; normalize light vector -dp3 r2.w, r2, r2 -rsq r2.w, r2.w -mul r2, r2, r2.w - -; calculate light color -dp3 r3, r1, r2 ; dp3 with negative light vector -lit r5, r3 ; clamp to zero if r3 < 0, r5 has diffuce component in r5.y -mul oD0, r5.y, c9 ; ouput diffuse color -mov oT0, v3 ; store texture coordinates
-
- - - - - - - -
D3D8.PSH
-; part of the Irrlicht Engine Shader example.
-; This simple Direct3D9 pixel shader will be loaded by the engine.
-; Please note that these example shaders don't do anything really useful. 
-; They only demonstrate that shaders can be used in Irrlicht.
-ps.1.1 - -tex t0 ; sample color map -mul_x2 t0, t0, v0 ; mulitply with color -add r0, t0, t0 ; make it brighter and store result
-
- - - - - - - -
OPENGL.VSH
-!!ARBvp1.0
-# part of the Irrlicht Engine Shader example.
-# Please note that these example shaders don't do anything really useful. 
-# They only demonstrate that shaders can be used in Irrlicht.
-#input -ATTRIB InPos = vertex.position; -ATTRIB InColor = vertex.color; -ATTRIB InNormal = vertex.normal; -ATTRIB InTexCoord = vertex.texcoord; - -#output -OUTPUT OutPos = result.position; -OUTPUT OutColor = result.color; -OUTPUT OutTexCoord = result.texcoord; - -PARAM MVP[4] = { state.matrix.mvp }; # modelViewProjection matrix. -TEMP Temp; -TEMP TempColor; -TEMP TempNormal; -TEMP TempPos; - -#transform position to clip space -DP4 Temp.x, MVP[0], InPos; -DP4 Temp.y, MVP[1], InPos; -DP4 Temp.z, MVP[2], InPos; -DP4 Temp.w, MVP[3], InPos; - -#transform normal -DP3 TempNormal.x, InNormal.x, program.local[0]; -DP3 TempNormal.y, InNormal.y, program.local[1]; -DP3 TempNormal.z, InNormal.z, program.local[2]; - -#renormalize normal -DP3 TempNormal.w, TempNormal, TempNormal; -RSQ TempNormal.w, TempNormal.w; -MUL TempNormal, TempNormal, TempNormal.w; - -# calculate light vector -DP4 TempPos.x, InPos, program.local[10]; # vertex into world position -DP4 TempPos.y, InPos, program.local[11]; -DP4 TempPos.z, InPos, program.local[12]; -DP4 TempPos.w, InPos, program.local[13]; - -ADD TempPos, program.local[8], -TempPos; # vtxpos - lightpos - -# normalize light vector -DP3 TempPos.w, TempPos, TempPos; -RSQ TempPos.w, TempPos.w; -MUL TempPos, TempPos, TempPos.w; - -# calculate light color -DP3 TempColor, TempNormal, TempPos; # dp3 with negative light vector -LIT OutColor, TempColor; # clamp to zero if r3 < 0, r5 has diffuce component in r5.y -MUL OutColor, TempColor.y, program.local[9]; # ouput diffuse color -MOV OutColor.w, 1.0; # we want alpha to be always 1 -MOV OutTexCoord, InTexCoord; # store texture coordinate -MOV OutPos, Temp; - -END
-
- - - - - - - -
OPENGL.PSH
-!!ARBfp1.0
-# part of the Irrlicht Engine Shader example.
-# Please note that these example shaders don't do anything really useful. 
-# They only demonstrate that shaders can be used in Irrlicht.
-#Input -ATTRIB inTexCoord = fragment.texcoord; # texture coordinates -ATTRIB inColor = fragment.color.primary; # interpolated diffuse color - -#Output -OUTPUT outColor = result.color; - -TEMP texelColor; -TEMP tmp; -TXP texelColor, inTexCoord, texture, 2D; - -ADD tmp, inColor, inColor; # mulitply with color -MUL texelColor, texelColor, tmp; # mulitply with color -ADD outColor, texelColor, texelColor; # make it brighter and store result - -END
-

 

-
-
-

 

-

 

- - + + + + + + + +Tutorial 10: Shaders + + + + +
+ + + +
+
+
+
Tutorial 10: Shaders
+
+
+
+010shot.jpg +
+

This tutorial shows how to use shaders for D3D9, and OpenGL with the engine and how to create new material types with them. It also shows how to disable the generation of mipmaps at texture loading, and how to use text scene nodes.

+

This tutorial does not explain how shaders work. I would recommend to read the D3D or OpenGL, documentation, to search a tutorial, or to read a book about this.

+

At first, we need to include all headers and do the stuff we always do, like in nearly all other tutorials:

#include <irrlicht.h>
#include <iostream>
#include "driverChoice.h"
#include "exampleHelper.h"
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

Because we want to use some interesting shaders in this tutorials, we need to set some data for them to make them able to compute nice colors. In this example, we'll use a simple vertex shader which will calculate the color of the vertex based on the position of the camera. For this, the shader needs the following data: The inverted world matrix for transforming the normal, the clip matrix for transforming the position, the camera position and the world position of the object for the calculation of the angle of light, and the color of the light. To be able to tell the shader all this data every frame, we have to derive a class from the IShaderConstantSetCallBack interface and override its only method, namely OnSetConstants(). This method will be called every time the material is set. The method setVertexShaderConstant() of the IMaterialRendererServices interface is used to set the data the shader needs. If the user chose to use a High Level shader language like HLSL instead of Assembler in this example, you have to set the variable name as parameter instead of the register index.

IrrlichtDevice* device = 0;
bool UseHighLevelShaders = false;
class MyShaderCallBack : public video::IShaderConstantSetCallBack
{
public:
MyShaderCallBack() : WorldViewProjID(-1), TransWorldID(-1), InvWorldID(-1), PositionID(-1),
ColorID(-1), TextureID(-1), FirstUpdate(true)
{
}
virtual void OnSetConstants(video::IMaterialRendererServices* services,
s32 userData)
{
video::IVideoDriver* driver = services->getVideoDriver();
// get shader constants id.
if (UseHighLevelShaders && FirstUpdate)
{
WorldViewProjID = services->getVertexShaderConstantID("mWorldViewProj");
TransWorldID = services->getVertexShaderConstantID("mTransWorld");
InvWorldID = services->getVertexShaderConstantID("mInvWorld");
PositionID = services->getVertexShaderConstantID("mLightPos");
ColorID = services->getVertexShaderConstantID("mLightColor");
// Textures ID are important only for OpenGL interface.
if(driver->getDriverType() == video::EDT_OPENGL)
TextureID = services->getVertexShaderConstantID("myTexture");
FirstUpdate = false;
}
// set inverted world matrix
// if we are using highlevel shaders (the user can select this when
// starting the program), we must set the constants by name.
core::matrix4 invWorld = driver->getTransform(video::ETS_WORLD);
invWorld.makeInverse();
if (UseHighLevelShaders)
services->setVertexShaderConstant(InvWorldID, invWorld.pointer(), 16);
else
services->setVertexShaderConstant(invWorld.pointer(), 0, 4);
// set clip matrix
core::matrix4 worldViewProj;
worldViewProj = driver->getTransform(video::ETS_PROJECTION);
worldViewProj *= driver->getTransform(video::ETS_VIEW);
worldViewProj *= driver->getTransform(video::ETS_WORLD);
if (UseHighLevelShaders)
services->setVertexShaderConstant(WorldViewProjID, worldViewProj.pointer(), 16);
else
services->setVertexShaderConstant(worldViewProj.pointer(), 4, 4);
// set camera position
core::vector3df pos = device->getSceneManager()->
getActiveCamera()->getAbsolutePosition();
if (UseHighLevelShaders)
services->setVertexShaderConstant(PositionID, reinterpret_cast<f32*>(&pos), 3);
else
services->setVertexShaderConstant(reinterpret_cast<f32*>(&pos), 8, 1);
// set light color
video::SColorf col(0.0f,1.0f,1.0f,0.0f);
if (UseHighLevelShaders)
services->setVertexShaderConstant(ColorID,
reinterpret_cast<f32*>(&col), 4);
else
services->setVertexShaderConstant(reinterpret_cast<f32*>(&col), 9, 1);
// set transposed world matrix
core::matrix4 world = driver->getTransform(video::ETS_WORLD);
world = world.getTransposed();
if (UseHighLevelShaders)
{
services->setVertexShaderConstant(TransWorldID, world.pointer(), 16);
// set texture, for textures you can use both an int and a float setPixelShaderConstant interfaces (You need it only for an OpenGL driver).
s32 TextureLayerID = 0;
services->setPixelShaderConstant(TextureID, &TextureLayerID, 1);
}
else
services->setVertexShaderConstant(world.pointer(), 10, 4);
}
private:
s32 WorldViewProjID;
s32 TransWorldID;
s32 InvWorldID;
s32 PositionID;
s32 ColorID;
s32 TextureID;
bool FirstUpdate;
};

The next few lines start up the engine just like in most other tutorials before. But in addition, we ask the user if he wants to use high level shaders in this example, if he selected a driver which is capable of doing so.

int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// ask the user if we should use high level shaders for this example
if (driverType == video::EDT_DIRECT3D9 ||
driverType == video::EDT_OPENGL)
{
char i = 'y';
printf("Please press 'y' if you want to use high level shaders.\n");
std::cin >> i;
if (i == 'y')
{
UseHighLevelShaders = true;
}
}
// create device
device = createDevice(driverType, core::dimension2d<u32>(640, 480));
if (device == 0)
return 1; // could not create selected driver.
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* gui = device->getGUIEnvironment();
const io::path mediaPath = getExampleMediaPath();

Now for the more interesting parts. If we are using Direct3D, we want to load vertex and pixel shader programs, if we have OpenGL, we want to use ARB fragment and vertex programs. I wrote the corresponding programs down into the files d3d9.ps, d3d9.vs, opengl.ps and opengl.vs. We only need the right filenames now. This is done in the following switch. Note, that it is not necessary to write the shaders into text files, like in this example. You can even write the shaders directly as strings into the cpp source file, and use later addShaderMaterial() instead of addShaderMaterialFromFiles().

io::path vsFileName; // filename for the vertex shader
io::path psFileName; // filename for the pixel shader
switch(driverType)
{
case video::EDT_DIRECT3D9:
if (UseHighLevelShaders)
{
psFileName = mediaPath + "d3d9.hlsl";
vsFileName = psFileName; // both shaders are in the same file
}
else
{
psFileName = mediaPath + "d3d9.psh";
vsFileName = mediaPath + "d3d9.vsh";
}
break;
case video::EDT_OPENGL:
if (UseHighLevelShaders)
{
psFileName = mediaPath + "opengl.frag";
vsFileName = mediaPath + "opengl.vert";
}
else
{
psFileName = mediaPath + "opengl.psh";
vsFileName = mediaPath + "opengl.vsh";
}
break;
default:
break;
}

In addition, we check if the hardware and the selected renderer is capable of executing the shaders we want. If not, we simply set the filename string to 0. This is not necessary, but useful in this example: For example, if the hardware is able to execute vertex shaders but not pixel shaders, we create a new material which only uses the vertex shader, and no pixel shader. Otherwise, if we would tell the engine to create this material and the engine sees that the hardware wouldn't be able to fulfill the request completely, it would not create any new material at all. So in this example you would see at least the vertex shader in action, without the pixel shader.

if (!driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1) &&
!driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1))
{
device->getLogger()->log("WARNING: Pixel shaders disabled "\
"because of missing driver/hardware support.");
psFileName = "";
}
if (!driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1) &&
!driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1))
{
device->getLogger()->log("WARNING: Vertex shaders disabled "\
"because of missing driver/hardware support.");
vsFileName = "";
}

Now lets create the new materials. As you maybe know from previous examples, a material type in the Irrlicht engine is set by simply changing the MaterialType value in the SMaterial struct. And this value is just a simple 32 bit value, like video::EMT_SOLID. So we only need the engine to create a new value for us which we can set there. To do this, we get a pointer to the IGPUProgrammingServices and call addShaderMaterialFromFiles(), which returns such a new 32 bit value. That's all.

+

The parameters to this method are the following: First, the names of the files containing the code of the vertex and the pixel shader. If you would use addShaderMaterial() instead, you would not need file names, then you could write the code of the shader directly as string. The following parameter is a pointer to the IShaderConstantSetCallBack class we wrote at the beginning of this tutorial. If you don't want to set constants, set this to 0. The last parameter tells the engine which material it should use as base material.

+

To demonstrate this, we create two materials with a different base material, one with EMT_SOLID and one with EMT_TRANSPARENT_ADD_COLOR.

// create materials
video::IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
s32 newMaterialType1 = 0;
s32 newMaterialType2 = 0;
if (gpu)
{

Create one callback instance for each shader material you add. Reason is that the getVertexShaderConstantID returns ID's which are only valid per added material (The ID's tend to be identical as long as the shader code is exactly identical, but it's not good style to depend on that).

MyShaderCallBack* mcSolid = new MyShaderCallBack();
MyShaderCallBack* mcTransparentAdd = new MyShaderCallBack();
// create the shaders depending on if the user wanted high level
// or low level shaders:
if (UseHighLevelShaders)
{
// create material from high level shaders (hlsl, glsl)
newMaterialType1 = gpu->addHighLevelShaderMaterialFromFiles(
vsFileName, "vertexMain", video::EVST_VS_1_1,
psFileName, "pixelMain", video::EPST_PS_1_1,
mcSolid, video::EMT_SOLID, 0);
newMaterialType2 = gpu->addHighLevelShaderMaterialFromFiles(
vsFileName, "vertexMain", video::EVST_VS_1_1,
psFileName, "pixelMain", video::EPST_PS_1_1,
mcTransparentAdd, video::EMT_TRANSPARENT_ADD_COLOR, 0);
}
else
{
// create material from low level shaders (asm or arb_asm)
newMaterialType1 = gpu->addShaderMaterialFromFiles(vsFileName,
psFileName, mcSolid, video::EMT_SOLID);
newMaterialType2 = gpu->addShaderMaterialFromFiles(vsFileName,
psFileName, mcTransparentAdd, video::EMT_TRANSPARENT_ADD_COLOR);
}
mcSolid->drop();
mcTransparentAdd->drop();
}

Now it's time for testing the materials. We create a test cube and set the material we created. In addition, we add a text scene node to the cube and a rotation animator to make it look more interesting and important.

// create test scene node 1, with the new created material type 1
scene::ISceneNode* node = smgr->addCubeSceneNode(50);
node->setPosition(core::vector3df(0,0,0));
node->setMaterialTexture(0, driver->getTexture(mediaPath + "wall.bmp"));
node->setMaterialFlag(video::EMF_LIGHTING, false);
node->setMaterialType((video::E_MATERIAL_TYPE)newMaterialType1);
smgr->addTextSceneNode(gui->getBuiltInFont(),
L"PS & VS & EMT_SOLID",
video::SColor(255,255,255,255), node);
scene::ISceneNodeAnimator* anim = smgr->createRotationAnimator(
core::vector3df(0,0.3f,0));
node->addAnimator(anim);
anim->drop();

Same for the second cube, but with the second material we created.

// create test scene node 2, with the new created material type 2
node = smgr->addCubeSceneNode(50);
node->setPosition(core::vector3df(0,-10,50));
node->setMaterialTexture(0, driver->getTexture(mediaPath + "wall.bmp"));
node->setMaterialFlag(video::EMF_LIGHTING, false);
node->setMaterialFlag(video::EMF_BLEND_OPERATION, true);
node->setMaterialType((video::E_MATERIAL_TYPE)newMaterialType2);
smgr->addTextSceneNode(gui->getBuiltInFont(),
L"PS & VS & EMT_TRANSPARENT",
video::SColor(255,255,255,255), node);
anim = smgr->createRotationAnimator(core::vector3df(0,0.3f,0));
node->addAnimator(anim);
anim->drop();

Then we add a third cube without a shader on it, to be able to compare the cubes.

// add a scene node with no shader
node = smgr->addCubeSceneNode(50);
node->setPosition(core::vector3df(0,50,25));
node->setMaterialTexture(0, driver->getTexture(mediaPath + "wall.bmp"));
node->setMaterialFlag(video::EMF_LIGHTING, false);
smgr->addTextSceneNode(gui->getBuiltInFont(), L"NO SHADER",
video::SColor(255,255,255,255), node);

And last, we add a skybox and a user controlled camera to the scene. For the skybox textures, we disable mipmap generation, because we don't need mipmaps on it.

// add a nice skybox
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
smgr->addSkyBoxSceneNode(
driver->getTexture(mediaPath + "irrlicht2_up.jpg"),
driver->getTexture(mediaPath + "irrlicht2_dn.jpg"),
driver->getTexture(mediaPath + "irrlicht2_lf.jpg"),
driver->getTexture(mediaPath + "irrlicht2_rt.jpg"),
driver->getTexture(mediaPath + "irrlicht2_ft.jpg"),
driver->getTexture(mediaPath + "irrlicht2_bk.jpg"));
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, true);
// add a camera and disable the mouse cursor
scene::ICameraSceneNode* cam = smgr->addCameraSceneNodeFPS();
cam->setPosition(core::vector3df(-100,50,100));
cam->setTarget(core::vector3df(0,0,0));
device->getCursorControl()->setVisible(false);

Now draw everything. That's all.

int lastFPS = -1;
while(device->run())
if (device->isWindowActive())
{
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(255,0,0,0));
smgr->drawAll();
driver->endScene();
int fps = driver->getFPS();
if (lastFPS != fps)
{
core::stringw str = L"Irrlicht Engine - Vertex and pixel shader example [";
str += driver->getName();
str += "] FPS:";
str += fps;
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}
device->drop();
return 0;
}

Compile and run this, and I hope you have fun with your new little shader writing tool :).

+
+ + +

 

+ + diff --git a/examples/11.PerPixelLighting/tutorial.html b/examples/11.PerPixelLighting/tutorial.html index 89e87bd8..fe1cda11 100644 --- a/examples/11.PerPixelLighting/tutorial.html +++ b/examples/11.PerPixelLighting/tutorial.html @@ -1,502 +1,206 @@ - - -Irrlicht Engine Tutorial - - - - -
- - - - - - - - -
-
-
Tutorial 11. Per pixel lighting
-
-
-
-

This tutorial shows how to use one of the built in more complex materials - in irrlicht: Per pixel lighted surfaces using normal maps and parallax - mapping. It will also show how to use fog and moving particle systems. - And don't panic: You dont need any experience with shaders to use these - materials in Irrlicht.

-

The program which is described here will look like this:

-


-

-
-
-
- - - - - - - -
Lets start!
-
-

At first, we need to include all headers and do the stuff we always - do, like in nearly all other tutorials.

- - - - -
#include <irrlicht.h>
#include <iostream>

using namespace irr;

#pragma comment(lib, "Irrlicht.lib")
-

For this example, we need an event receiver, to make it possible - for the user to switch between the three available material types. - In addition, the event receiver will create some small GUI window - which displays what material is currently being used. There is nothing - special done in this class, so maybe you want to skip reading it.

- - - - -
class MyEventReceiver : public IEventReceiver
-{
-public:
-
-	MyEventReceiver(scene::ISceneNode* room, 
-		gui::IGUIEnvironment* env, video::IVideoDriver* driver)
-	{
-		// store pointer to room so we can change its drawing mode
-		Room = room;
-		Driver = driver;
-
-		// set a nicer font
-		gui::IGUISkin* skin = env->getSkin();
-		gui::IGUIFont* font = env->getFont("../../media/fonthaettenschweiler.bmp");
-		if (font)
-			skin->setFont(font);
-
-		// add window and listbox
-		gui::IGUIWindow* window = env->addWindow(
-			core::rect(490,390,630,470), false, L"Use 'E' + 'R' to change");
-
-		ListBox = env->addListBox(
-			core::rect(2,22,135,78), window);
-
-		ListBox->addItem(L"Diffuse");
-		ListBox->addItem(L"Bump mapping");
-		ListBox->addItem(L"Parallax mapping");
-		ListBox->setSelected(1);
-
-		// create problem text
-		ProblemText = env->addStaticText(
-			L"Your hardware or this renderer is not able to use the "\
-			L"needed shaders for this material. Using fall back materials.",
-			core::rect(150,20,470,60));
-
-		ProblemText->setOverrideColor(video::SColor(100,255,255,255));
-
-		// set start material (prefer parallax mapping if available)
-		video::IMaterialRenderer* renderer = 
-			Driver->getMaterialRenderer(video::EMT_PARALLAX_MAP_SOLID);
-		if (renderer && renderer->getRenderCapability() == 0)
-			ListBox->setSelected(2);
-
-		// set the material which is selected in the listbox
-		setMaterial();
-	}
-
-	bool OnEvent(const SEvent& event)
-	{
-		// check if user presses the key 'E' or 'R'
-		if (event.EventType == irr::EET_KEY_INPUT_EVENT &&
-			!event.KeyInput.PressedDown && Room && ListBox)
-		{
-			// change selected item in listbox 
-
-			int sel = ListBox->getSelected();
-			if (event.KeyInput.Key == irr::KEY_KEY_R)
-				++sel;
-			else
-			if (event.KeyInput.Key == irr::KEY_KEY_E)
-				--sel;
-			else 
-				return false;
-
-			if (sel > 2) sel = 0;
-			if (sel < 0) sel = 2;
-			ListBox->setSelected(sel);
-			
-			// set the material which is selected in the listbox
-			setMaterial();
-		}
-
-		return false;
-	}
-
-private:
-
-	// sets the material of the room mesh the the one set in the 
-	// list box.
-	void setMaterial()
-	{
-		video::E_MATERIAL_TYPE type = video::EMT_SOLID;
-
-		// change material setting
-		switch(ListBox->getSelected())
-		{
-		case 0: type = video::EMT_SOLID;
-			break;
-		case 1: type = video::EMT_NORMAL_MAP_SOLID;
-			break;
-		case 2: type = video::EMT_PARALLAX_MAP_SOLID;
-			break;
-		}
-
-		Room->setMaterialType(type);
-
-

We need to add a warning if the materials will not be able to be - displayed 100% correctly. This is no problem, they will be renderered - using fall back materials, but at least the user should know that - it would look better on better hardware. We simply check if the material - renderer is able to draw at full quality on the current hardware. - The IMaterialRenderer::getRenderCapability() returns 0 if this is - the case.
-

- - - - -
video::IMaterialRenderer* renderer = Driver->getMaterialRenderer(type);
-
-		// display some problem text when problem
-		if (!renderer || renderer->getRenderCapability() != 0)
-			ProblemText->setVisible(true);
-		else
-			ProblemText->setVisible(false);
-	}
-
-private:
-
-	gui::IGUIStaticText* ProblemText;
-	gui::IGUIListBox* ListBox;
-
-	scene::ISceneNode* Room;	
-	video::IVideoDriver* Driver;
-};
-


- Now for the real fun. We create an Irrlicht Device and start to setup - the scene.
-

- - - - -
int main()
-{
-	// let user select driver type
-
-	video::E_DRIVER_TYPE driverType = video::EDT_DIRECT3D9;
-
printf("Please select the driver you want for this example:\n"\
" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
" (d) Software Renderer\n (e) Apfelbaum Software Renderer\n"\
" (f) NullDevice\n (otherKey) exit\n\n");
- char i; - std::cin >> i; - - switch(i)
{
case 'a': driverType = video::EDT_DIRECT3D9;break;
case 'b': driverType = video::EDT_DIRECT3D8;break;
case 'c': driverType = video::EDT_OPENGL; break;
case 'd': driverType = video::EDT_SOFTWARE; break;
case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
case 'f': driverType = video::EDT_NULL; break;
default: return 0;
} - - // create device - - IrrlichtDevice* device = createDevice(driverType, core::dimension2d(640, 480)); - - if (device == 0) - return 1; // could not create selected driver. -
-
- Before we start with the interesting stuff, we do some simple things: - Store pointers to the most important parts of the engine (video driver,
- scene manager, gui environment) to safe us from typing too much, add - an irrlicht engine logo to the window and a user controlled first person - shooter style camera. Also, we let the engine now that it should store - all textures in 32 bit. This necessary because for parallax mapping, - we need 32 bit textures.
-
- - - - -
-	video::IVideoDriver* driver = device->getVideoDriver();
-	scene::ISceneManager* smgr = device->getSceneManager();
-	gui::IGUIEnvironment* env = device->getGUIEnvironment();
-
-	driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true);
-
-	// add irrlicht logo
-	env->addImage(driver->getTexture("../../media/irrlichtlogoalpha.tga"),
-		core::position2d(10,10));
-		
-	// add camera
-	scene::ICameraSceneNode* camera = 
-		smgr->addCameraSceneNodeFPS(0,100.0f,300.0f);
-	camera->setPosition(core::vector3df(-200,200,-200));
-
-	// disable mouse cursor
-	device->getCursorControl()->setVisible(false);
-
- Because we want the whole scene to look a little bit scarier, we add - some fog to it. This is done by a call to IVideoDriver::setFog(). There - you can set
- various fog settings. In this example, we use pixel fog, because it - will work well with the materials we'll use in this example. Please - note that you will have to set the material flag EMF_FOG_ENABLE to 'true' - in every scene node which should be affected by this fog.
-
- - - - -
driver->setFog(video::SColor(0,138,125,81), true, 250, 1000, 0, true);
-
- To be able to display something interesting, we load a mesh from a .3ds - file which is a room I modeled with anim8or. It is the same room as -
- from the specialFX example. Maybe you remember from that tutorial, I - am no good modeler at all and so I totally messed up the texture mapping - in this model, but we can simply repair it with the IMeshManipulator::makePlanarTextureMapping() - method.
-
- - - - -
	scene::IAnimatedMesh* roomMesh = smgr->getMesh(
-		"../../media/room.3ds");
-	scene::ISceneNode* room = 0;
-
-	if (roomMesh)
-	{
-		smgr->getMeshManipulator()->makePlanarTextureMapping(
-				roomMesh->getMesh(0), 0.003f);
-
- Now for the first exciting thing: If we successfully loaded the mesh - we need to apply textures to it. Because we want this room to be displayed - with a very cool material, we have to do a little bit more than just - set the textures. Instead of only loading a color map as usual, we also - load a height map which is simply a grayscale texture. From this height - map, we create a normal map which we will set as second texture of the - room. If you already have a normal map, you could directly set it, but - I simply didn´t find a nice normal map for this texture. The normal - map texture is being generated by the makeNormalMapTexture method
- of the VideoDriver. The second parameter specifies the height of the - heightmap. If you set it to a bigger value, the map will look more rocky.
-
- - - - -
		video::ITexture* colorMap = driver->getTexture("../../media/rockwall.bmp");
-		video::ITexture* normalMap = driver->getTexture("../../media/rockwall_height.bmp");
-		
-		driver->makeNormalMapTexture(normalMap, 9.0f);
-
- But just setting color and normal map is not everything. The material - we want to use needs some additional informations per vertex like tangents - and binormals.
- Because we are too lazy to calculate that information now, we let Irrlicht - do this for us. That's why we call IMeshManipulator::createMeshWithTangents(). - It
- creates a mesh copy with tangents and binormals from any other mesh. - After we've done that, we simply create a standard mesh scene node with - this
- mesh copy, set color and normal map and adjust some other material settings. - Note that we set EMF_FOG_ENABLE to true to enable fog in the room.
-
- - - - -
scene::IMesh* tangentMesh = smgr->getMeshManipulator()->createMeshWithTangents(
roomMesh->getMesh(0));

room = smgr->addMeshSceneNode(tangentMesh);
room->setMaterialTexture(0, colorMap);
room->setMaterialTexture(1, normalMap);
room->getMaterial(0).SpecularColor.set(0,0,0,0);
room->setMaterialFlag(video::EMF_FOG_ENABLE, true);
room->setMaterialType(video::EMT_PARALLAX_MAP_SOLID);
room->getMaterial(0).MaterialTypeParam = 0.02f; // adjust height for parallax effect
// drop mesh because we created it with a create.. call.
tangentMesh->drop();
}
-
- After we've created a room shaded by per pixel lighting, we add a sphere - into it with the same material, but we'll make it transparent. In addition,
- because the sphere looks somehow like a familiar planet, we make it - rotate. The procedure is similar as before. The difference is that we - are loading
- the mesh from an .x file which already contains a color map so we do - not need to load it manually. But the sphere is a little bit too small - for our needs, so we scale it by the factor 50.
-
- - - - -
// add earth sphere
-
-	scene::IAnimatedMesh* earthMesh = smgr->getMesh("../../media/earth.x");
-	if (earthMesh)
-	{
-		// create mesh copy with tangent informations from original earth.x mesh
-		scene::IMesh* tangentSphereMesh = 
-			smgr->getMeshManipulator()->createMeshWithTangents(earthMesh->getMesh(0));
-
-		// set the alpha value of all vertices to 200
-		smgr->getMeshManipulator()->setVertexColorAlpha(tangentSphereMesh, 200);
-		
-		// scale the mesh by factor 50
-		smgr->getMeshManipulator()->scaleMesh(
-			tangentSphereMesh, core::vector3df(50,50,50));
-
-		// create mesh scene node
-		scene::ISceneNode* sphere = smgr->addMeshSceneNode(tangentSphereMesh);
-		sphere->setPosition(core::vector3df(-70,130,45));
-
-		// load heightmap, create normal map from it and set it
-		video::ITexture* earthNormalMap = driver->getTexture("../../media/earthbump.bmp");
-		driver->makeNormalMapTexture(earthNormalMap, 20.0f);
-		sphere->setMaterialTexture(1, earthNormalMap);
-
-		// adjust material settings
-		sphere->setMaterialFlag(video::EMF_FOG_ENABLE, true);
-		sphere->setMaterialType(video::EMT_NORMAL_MAP_TRANSPARENT_VERTEX_ALPHA); 
-
-		// add rotation animator
-		scene::ISceneNodeAnimator* anim =
-			smgr->createRotationAnimator(core::vector3df(0,0.1f,0));	
-		sphere->addAnimator(anim);
-		anim->drop();
-
-		// drop mesh because we created it with a create.. call.
-		tangentSphereMesh->drop();
-	}
-
- Per pixel lighted materials only look cool when there are moving lights. - So we add some. And because moving lights alone are so boring, we add - billboards
- to them, and a whole particle system to one of them. We start with the - first light which is red and has only the billboard attached.
-
- - - - -
// add light 1 (nearly red)
-	scene::ILightSceneNode* light1 = 
-		smgr->addLightSceneNode(0, core::vector3df(0,0,0), 
-		video::SColorf(0.5f, 1.0f, 0.5f, 0.0f), 200.0f);
-
-	// add fly circle animator to light 1
-	scene::ISceneNodeAnimator* anim = 
-		smgr->createFlyCircleAnimator (core::vector3df(50,300,0),190.0f, -0.003f);
-	light1->addAnimator(anim);
-	anim->drop();
-
-	// attach billboard to the light
-	scene::ISceneNode* bill = 
-		smgr->addBillboardSceneNode(light1, core::dimension2d(60, 60));
-
-	bill->setMaterialFlag(video::EMF_LIGHTING, false);
-	bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
-	bill->setMaterialTexture(0, driver->getTexture("../../media/particlered.bmp"));
-
- Now the same again, with the second light. The difference is that we - add a particle system to it too. And because the light moves, the particles - of the particlesystem will follow. If you want to know more about how - particle systems are created in Irrlicht, take a look at the specialFx - example.
- Maybe you will have noticed that we only add 2 lights, this has a simple - reason: The low end version of this material was written in ps1.1 and - vs1.1, which doesn't allow more lights. You could add a third light - to the scene, but it won't be used to shade the walls. But of course, - this will change in future versions of Irrlicht were higher versions - of pixel/vertex shaders will be implemented too.
-
- - - - -
// add light 2 (gray)
-	scene::ISceneNode* light2 = 
-		smgr->addLightSceneNode(0, core::vector3df(0,0,0), 
-		video::SColorf(1.0f, 0.2f, 0.2f, 0.0f), 200.0f);
-
-	// add fly circle animator to light 2
-	anim = smgr->createFlyCircleAnimator (core::vector3df(0,150,0),200.0f); 
-	light2->addAnimator(anim);
-	anim->drop();
-
-	// attach billboard to light
-	bill = smgr->addBillboardSceneNode(light2, core::dimension2d(120, 120));
-	bill->setMaterialFlag(video::EMF_LIGHTING, false);
-	bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
-	bill->setMaterialTexture(0, driver->getTexture("../../media/particlewhite.bmp"));
-
-	// add particle system
-	scene::IParticleSystemSceneNode* ps = 
-		smgr->addParticleSystemSceneNode(false, light2);
-
-	ps->setParticleSize(core::dimension2d(30.0f, 40.0f));
-
-	// create and set emitter
-	scene::IParticleEmitter* em = ps->createBoxEmitter(
-		core::aabbox3d(-3,0,-3,3,1,3), 
-		core::vector3df(0.0f,0.03f,0.0f),
-		80,100, 
-		video::SColor(0,255,255,255), video::SColor(0,255,255,255),
-		400,1100);
-	ps->setEmitter(em);
-	em->drop();
-
-	// create and set affector
-	scene::IParticleAffector* paf = ps->createFadeOutParticleAffector();
-	ps->addAffector(paf);
-	paf->drop();
-
-	// adjust some material settings
-	ps->setMaterialFlag(video::EMF_LIGHTING, false);
-	ps->setMaterialTexture(0, driver->getTexture("../../media/fireball.bmp"));
-	ps->setMaterialType(video::EMT_TRANSPARENT_VERTEX_ALPHA);
-
-
-	MyEventReceiver receiver(room, env, driver);
-	device->setEventReceiver(&receiver);
-
- Finally, draw everything. That's it.
-
- - - - -
int lastFPS = -1;
-
-	while(device->run())
-	if (device->isWindowActive())
-	{
-		driver->beginScene(true, true, 0);
-
-		smgr->drawAll();
-		env->drawAll();
-
-		driver->endScene();
-
-		int fps = driver->getFPS();
-
-		if (lastFPS != fps)
-		{
-		  core::stringw str = L"Per pixel lighting example - Irrlicht Engine [";
-		  str += driver->getName();
-		  str += "] FPS:";
-		  str += fps;
-
-		  device->setWindowCaption(str.c_str());
-		  lastFPS = fps;
-		}
-	}
-
-	device->drop();
-	
-	return 0;
-}
-
-
-
-
-
-

 

- - + + + + + + + +Tutorial 11: Per-Pixel Lighting + + + + +
+ + + +
+
+
+
Tutorial 11: Per-Pixel Lighting
+
+
+
+011shot.jpg +
+

This tutorial shows how to use one of the built in more complex materials in irrlicht: Per pixel lighted surfaces using normal maps and parallax mapping. It will also show how to use fog and moving particle systems. And don't panic: You do not need any experience with shaders to use these materials in Irrlicht.

+

At first, we need to include all headers and do the stuff we always do, like in nearly all other tutorials.

#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

For this example, we need an event receiver, to make it possible for the user to switch between the three available material types. In addition, the event receiver will create some small GUI window which displays what material is currently being used. There is nothing special done in this class, so maybe you want to skip reading it.

class MyEventReceiver : public IEventReceiver
{
public:
MyEventReceiver(scene::ISceneNode* room,scene::ISceneNode* earth,
gui::IGUIEnvironment* env, video::IVideoDriver* driver)
{
// store pointer to room so we can change its drawing mode
Room = room;
Earth = earth;
Driver = driver;
// set a nicer font
gui::IGUISkin* skin = env->getSkin();
gui::IGUIFont* font = env->getFont(getExampleMediaPath() + "fonthaettenschweiler.bmp");
if (font)
skin->setFont(font);
// add window and listbox
gui::IGUIWindow* window = env->addWindow(
core::rect<s32>(460,375,630,470), false, L"Use 'E' + 'R' to change");
ListBox = env->addListBox(
core::rect<s32>(2,22,165,88), window);
ListBox->addItem(L"Diffuse");
ListBox->addItem(L"Bump mapping");
ListBox->addItem(L"Parallax mapping");
ListBox->setSelected(1);
// create problem text
ProblemText = env->addStaticText(
L"Your hardware or this renderer is not able to use the "\
L"needed shaders for this material. Using fall back materials.",
core::rect<s32>(150,20,470,80));
ProblemText->setOverrideColor(video::SColor(100,255,255,255));
// set start material (prefer parallax mapping if available)
video::IMaterialRenderer* renderer =
Driver->getMaterialRenderer(video::EMT_PARALLAX_MAP_SOLID);
if (renderer && renderer->getRenderCapability() == 0)
ListBox->setSelected(2);
// set the material which is selected in the listbox
setMaterial();
}
bool OnEvent(const SEvent& event)
{
// check if user presses the key 'E' or 'R'
if (event.EventType == irr::EET_KEY_INPUT_EVENT &&
!event.KeyInput.PressedDown && Room && ListBox)
{
// change selected item in listbox
int sel = ListBox->getSelected();
if (event.KeyInput.Key == irr::KEY_KEY_R)
++sel;
else
if (event.KeyInput.Key == irr::KEY_KEY_E)
--sel;
else
return false;
if (sel > 2) sel = 0;
if (sel < 0) sel = 2;
ListBox->setSelected(sel);
// set the material which is selected in the listbox
setMaterial();
}
return false;
}
private:
// sets the material of the room mesh the the one set in the
// list box.
void setMaterial()
{
video::E_MATERIAL_TYPE type = video::EMT_SOLID;
// change material setting
switch(ListBox->getSelected())
{
case 0: type = video::EMT_SOLID;
break;
case 1: type = video::EMT_NORMAL_MAP_SOLID;
break;
case 2: type = video::EMT_PARALLAX_MAP_SOLID;
break;
}
Room->setMaterialType(type);
// change material setting
switch(ListBox->getSelected())
{
case 0: type = video::EMT_TRANSPARENT_VERTEX_ALPHA;
break;
case 1: type = video::EMT_NORMAL_MAP_TRANSPARENT_VERTEX_ALPHA;
break;
case 2: type = video::EMT_PARALLAX_MAP_TRANSPARENT_VERTEX_ALPHA;
break;
}
Earth->setMaterialType(type);

We need to add a warning if the materials will not be able to be displayed 100% correctly. This is no problem, they will be rendered using fall back materials, but at least the user should know that it would look better on better hardware. We simply check if the material renderer is able to draw at full quality on the current hardware. The IMaterialRenderer::getRenderCapability() returns 0 if this is the case.

video::IMaterialRenderer* renderer = Driver->getMaterialRenderer(type);
// display some problem text when problem
if (!renderer || renderer->getRenderCapability() != 0)
ProblemText->setVisible(true);
else
ProblemText->setVisible(false);
}
private:
gui::IGUIStaticText* ProblemText;
gui::IGUIListBox* ListBox;
scene::ISceneNode* Room;
scene::ISceneNode* Earth;
video::IVideoDriver* Driver;
};

Now for the real fun. We create an Irrlicht Device and start to setup the scene.

int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// create device
IrrlichtDevice* device = createDevice(driverType,
core::dimension2d<u32>(640, 480));
if (device == 0)
return 1; // could not create selected driver.

Before we start with the interesting stuff, we do some simple things: Store pointers to the most important parts of the engine (video driver, scene manager, gui environment) to safe us from typing too much, add an irrlicht engine logo to the window and a user controlled first person shooter style camera. Also, we let the engine know that it should store all textures in 32 bit. This necessary because for parallax mapping, we need 32 bit textures.

video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* env = device->getGUIEnvironment();
driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true);
const io::path mediaPath = getExampleMediaPath();
// add irrlicht logo
env->addImage(driver->getTexture(mediaPath + "irrlichtlogo3.png"),
core::position2d<s32>(10,10));
// add camera
scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();
camera->setPosition(core::vector3df(-200,200,-200));
// disable mouse cursor
device->getCursorControl()->setVisible(false);

Because we want the whole scene to look a little bit scarier, we add some fog to it. This is done by a call to IVideoDriver::setFog(). There you can set various fog settings. In this example, we use pixel fog, because it will work well with the materials we'll use in this example. Please note that you will have to set the material flag EMF_FOG_ENABLE to 'true' in every scene node which should be affected by this fog.

driver->setFog(video::SColor(0,138,125,81), video::EFT_FOG_LINEAR, 250, 1000, .003f, true, false);

To be able to display something interesting, we load a mesh from a .3ds file which is a room I modeled with anim8or. It is the same room as from the specialFX example. Maybe you remember from that tutorial, I am no good modeler at all and so I totally messed up the texture mapping in this model, but we can simply repair it with the IMeshManipulator::makePlanarTextureMapping() method.

scene::IAnimatedMesh* roomMesh = smgr->getMesh(mediaPath + "room.3ds");
scene::ISceneNode* room = 0;
scene::ISceneNode* earth = 0;
if (roomMesh)
{
// The room mesh doesn't have proper texture mapping on the
// floor, so we can recreate the mapping on runtime.
smgr->getMeshManipulator()->makePlanarTextureMapping(
roomMesh->getMesh(0), 0.003f);

Now for the first exciting thing: If we successfully loaded the mesh we need to apply textures to it. Because we want this room to be displayed with a very cool material, we have to do a little bit more than just set the textures. Instead of only loading a color map as usual, we also load a height map which is simply a grayscale texture. From this height map, we create a normal map which we will set as second texture of the room. If you already have a normal map, you could directly set it, but I simply didn't find a nice normal map for this texture. The normal map texture is being generated by the makeNormalMapTexture method of the VideoDriver. The second parameter specifies the height of the heightmap. If you set it to a bigger value, the map will look more rocky.

video::ITexture* normalMap =
driver->getTexture(mediaPath + "rockwall_height.bmp");
if (normalMap)
driver->makeNormalMapTexture(normalMap, 9.0f);

But just setting color and normal map is not everything. The material we want to use needs some additional information per vertex like tangents and binormals. Because we are too lazy to calculate that information now, we let Irrlicht do this for us. That's why we call IMeshManipulator::createMeshWithTangents(). It creates a mesh copy with tangents and binormals from another mesh. After we've done that, we simply create a standard mesh scene node with this mesh copy, set color and normal map and adjust some other material settings. Note that we set EMF_FOG_ENABLE to true to enable fog in the room.

scene::IMesh* tangentMesh = smgr->getMeshManipulator()->
createMeshWithTangents(roomMesh->getMesh(0));
room = smgr->addMeshSceneNode(tangentMesh);
room->setMaterialTexture(0,
driver->getTexture(mediaPath + "rockwall.jpg"));
room->setMaterialTexture(1, normalMap);
// Stones don't glitter.. (but specular highlight for EMT_SOLID)
//room->getMaterial(0).SpecularColor.set(0,0,0,0);
//room->getMaterial(0).Shininess = 0.f;
room->setMaterialFlag(video::EMF_FOG_ENABLE, true);
room->setMaterialType(video::EMT_PARALLAX_MAP_SOLID);
// adjust height for parallax effect
room->getMaterial(0).MaterialTypeParam = 1.f / 64.f;
// drop mesh because we created it with a create.. call.
tangentMesh->drop();
}

After we've created a room shaded by per pixel lighting, we add a sphere into it with the same material, but we'll make it transparent. In addition, because the sphere looks somehow like a familiar planet, we make it rotate. The procedure is similar as before. The difference is that we are loading the mesh from an .x file which already contains a color map so we do not need to load it manually. But the sphere is a little bit too small for our needs, so we scale it by the factor 50.

// add earth sphere
scene::IAnimatedMesh* earthMesh = smgr->getMesh(mediaPath + "earth.x");
if (earthMesh)
{
//perform various tasks with the mesh manipulator
scene::IMeshManipulator *manipulator = smgr->getMeshManipulator();
// create mesh copy with tangent information from original earth.x mesh
scene::IMesh* tangentSphereMesh =
manipulator->createMeshWithTangents(earthMesh->getMesh(0));
// set the alpha value of all vertices to 200
manipulator->setVertexColorAlpha(tangentSphereMesh, 200);
// scale the mesh by factor 50
core::matrix4 m;
m.setScale ( core::vector3df(50,50,50) );
manipulator->transform( tangentSphereMesh, m );
earth = smgr->addMeshSceneNode(tangentSphereMesh);
earth->setPosition(core::vector3df(-70,130,45));
// load heightmap, create normal map from it and set it
video::ITexture* earthNormalMap = driver->getTexture(mediaPath + "earthbump.jpg");
if (earthNormalMap)
{
driver->makeNormalMapTexture(earthNormalMap, 20.0f);
earth->setMaterialTexture(1, earthNormalMap);
earth->setMaterialType(video::EMT_NORMAL_MAP_TRANSPARENT_VERTEX_ALPHA);
}
// adjust material settings
earth->setMaterialFlag(video::EMF_FOG_ENABLE, true);
// add rotation animator
scene::ISceneNodeAnimator* anim =
smgr->createRotationAnimator(core::vector3df(0,0.1f,0));
earth->addAnimator(anim);
anim->drop();
// drop mesh because we created it with a create.. call.
tangentSphereMesh->drop();
}

Per pixel lighted materials only look cool when there are moving lights. So we add some. And because moving lights alone are so boring, we add billboards to them, and a whole particle system to one of them. We start with the first light which is red and has only the billboard attached.

// add light 1 (more green)
scene::ILightSceneNode* light1 =
smgr->addLightSceneNode(0, core::vector3df(0,0,0),
video::SColorf(0.5f, 1.0f, 0.5f, 0.0f), 400.0f);
// add fly circle animator to light 1
scene::ISceneNodeAnimator* anim =
smgr->createFlyCircleAnimator (core::vector3df(50,300,0),190.0f, -0.003f);
light1->addAnimator(anim);
anim->drop();
// attach billboard to the light
scene::IBillboardSceneNode* bill =
smgr->addBillboardSceneNode(light1, core::dimension2d<f32>(60, 60));
bill->setMaterialFlag(video::EMF_LIGHTING, false);
bill->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
bill->setMaterialTexture(0, driver->getTexture(mediaPath + "particlegreen.jpg"));

Now the same again, with the second light. The difference is that we add a particle system to it too. And because the light moves, the particles of the particle system will follow. If you want to know more about how particle systems are created in Irrlicht, take a look at the SpecialFX example. Maybe you will have noticed that we only add 2 lights, this has a simple reason: The low end version of this material was written in ps1.1 and vs1.1, which doesn't allow more lights. You could add a third light to the scene, but it won't be used to shade the walls. But of course, this will change in future versions of Irrlicht where higher versions of pixel/vertex shaders will be implemented too.

// add light 2 (red)
scene::ISceneNode* light2 =
smgr->addLightSceneNode(0, core::vector3df(0,0,0),
video::SColorf(1.0f, 0.2f, 0.2f, 0.0f), 400.0f);
// add fly circle animator to light 2
anim = smgr->createFlyCircleAnimator(core::vector3df(0,150,0), 200.0f,
0.001f, core::vector3df(0.2f, 0.9f, 0.f));
light2->addAnimator(anim);
anim->drop();
// attach billboard to light
bill = smgr->addBillboardSceneNode(light2, core::dimension2d<f32>(120, 120));
bill->setMaterialFlag(video::EMF_LIGHTING, false);
bill->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
bill->setMaterialTexture(0, driver->getTexture(mediaPath + "particlered.bmp"));
// add particle system
scene::IParticleSystemSceneNode* ps =
smgr->addParticleSystemSceneNode(false, light2);
// create and set emitter
scene::IParticleEmitter* em = ps->createBoxEmitter(
core::aabbox3d<f32>(-3,0,-3,3,1,3),
core::vector3df(0.0f,0.03f,0.0f),
80,100,
video::SColor(10,255,255,255), video::SColor(10,255,255,255),
400,1100);
em->setMinStartSize(core::dimension2d<f32>(30.0f, 40.0f));
em->setMaxStartSize(core::dimension2d<f32>(30.0f, 40.0f));
ps->setEmitter(em);
em->drop();
// create and set affector
scene::IParticleAffector* paf = ps->createFadeOutParticleAffector();
ps->addAffector(paf);
paf->drop();
// adjust some material settings
ps->setMaterialFlag(video::EMF_LIGHTING, false);
ps->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
ps->setMaterialTexture(0, driver->getTexture(mediaPath + "fireball.bmp"));
ps->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
MyEventReceiver receiver(room, earth, env, driver);
device->setEventReceiver(&receiver);

Finally, draw everything. That's it.

int lastFPS = -1;
while(device->run())
if (device->isWindowActive())
{
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(0));
smgr->drawAll();
env->drawAll();
driver->endScene();
int fps = driver->getFPS();
if (lastFPS != fps)
{
core::stringw str = L"Per pixel lighting example - Irrlicht Engine [";
str += driver->getName();
str += "] FPS:";
str += fps;
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}
device->drop();
return 0;
}
+ + +

 

+ + diff --git a/examples/12.TerrainRendering/tutorial.html b/examples/12.TerrainRendering/tutorial.html index f3d765f9..079e7cc4 100644 --- a/examples/12.TerrainRendering/tutorial.html +++ b/examples/12.TerrainRendering/tutorial.html @@ -1,122 +1,209 @@ - - -Irrlicht Engine Tutorial - - - - -
- - - - - - - - -
-
-
Tutorial 12. Terrain Rendering
-
-
-
-

This tutorial will briefly show how to use the terrain renderer of - Irrlicht. It will also show the terrain renderer triangle selector to - be able to do collision detection with terrain.

-

The program which is described here will look like this:

-

-


- Note that the terrain renderer in Irrlicht is based the terrain renderer - by Soconne and the GeoMipMapSceneNode developed by Spinz, lots of thanks - go to them.

-
-
-
- - - - - - - -
Lets start!
-
-

In the beginning there is nothing special. We include the needed - header files and create an event listener to listen if the user presses - the 'W' key so we can switch to wireframe mode and if he presses 'D' - we toggle to material between solid and detail mapped.

- - - - -
#include <irrlicht.h>
#include <iostream>
using namespace irr;

#pragma comment(lib, "Irrlicht.lib")

class MyEventReceiver : public IEventReceiver
{
public:
MyEventReceiver(scene::ISceneNode* terrain)
{
// store pointer to terrain so we can change its drawing mode
Terrain = terrain;
}

bool OnEvent(const SEvent& event)
{
// check if user presses the key 'W' or 'D'
if (event.EventType == irr::EET_KEY_INPUT_EVENT && !event.KeyInput.PressedDown)
{
switch (event.KeyInput.Key)
{
case irr::KEY_KEY_W: // switch wire frame mode
Terrain->setMaterialFlag(video::EMF_WIREFRAME, !Terrain->getMaterial(0).Wireframe);
return true;
case irr::KEY_KEY_D: // toggle detail map
Terrain->setMaterialType(
Terrain->getMaterial(0).MaterialType == video::EMT_SOLID ?
video::EMT_DETAIL_MAP : video::EMT_SOLID);
return true;
}
}
return false;
}

private:
scene::ISceneNode* Terrain;
};

-

The start of the main function starts like in most other example. - We ask the user for the desired renderer and start it up.

- - - - -
int main()
{
// let user select driver type

video::E_DRIVER_TYPE driverType = video::EDT_DIRECT3D9;

printf("Please select the driver you want for this example:\n"\
" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
" (d) Software Renderer\n (e) Apfelbaum Software Renderer\n"\
" (f) NullDevice\n (otherKey) exit\n\n");

char i;
std::cin >> i;

switch(i)
{
case 'a': driverType = video::EDT_DIRECT3D9;break;
case 'b': driverType = video::EDT_DIRECT3D8;break;
case 'c': driverType = video::EDT_OPENGL; break;
case 'd': driverType = video::EDT_SOFTWARE; break;
case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
case 'f': driverType = video::EDT_NULL; break;
default: return 1;
}

// create device
IrrlichtDevice* device = createDevice(driverType, core::dimension2d<s32>(640, 480));

if (device == 0)
return 1; // could not create selected driver.
-

First, we add standard stuff to the scene: A nice irrlicht engine - logo, a small help text, a user controlled camera, and we disable - the mouse cursor.

- - - - -
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* env = device->getGUIEnvironment();

driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true);

// add irrlicht logo
env->addImage(driver->getTexture("../../media/irrlichtlogoalpha.tga"),
core::position2d<s32>(10,10));
-// add some help text
gui::IGUIStaticText* text = env->addStaticText(
L"Press 'W' to change wireframe mode\nPress 'D' to toggle detail map",
core::rect<s32>(10,453,200,475), true, true, 0, -1, true);
-// add camera
scene::ICameraSceneNode* camera =
smgr->addCameraSceneNodeFPS(0,100.0f,1200.0f);
camera->setPosition(core::vector3df(1900*2,255*2,3700*2));
camera->setTarget(core::vector3df(2397*2,343*2,2700*2));
camera->setFarValue(12000.0f);

// disable mouse cursor
device->getCursorControl()->setVisible(false);
-

Here comes the terrain renderer scene node: We add it just like - any other scene node to the scene using ISceneManager::addTerrainSceneNode(). - The only parameter we use is a file name to the heightmap we use. - A heightmap is simply a gray scale texture. The terrain renderer loads - it and creates the 3D terrain from it.
- To make the terrain look more big, we change the scale factor of it - to (40, 4.4, 40). Because we don't have any dynamic lights in the - scene, we switch off the lighting, and we set the file terrain-texture.jpg - as texture for the terrain and detailmap3.jpg as second texture, called - detail map. At last, we set the scale values for the texture: The - first texture will be repeated only one time over the whole terrain, - and the second one (detail map) 20 times.

- - - - -
// add terrain scene node
scene::ITerrainSceneNode* terrain = smgr->addTerrainSceneNode(
"../../media/terrain-heightmap.bmp");

terrain->setScale(core::vector3df(40, 4.4f, 40));
terrain->setMaterialFlag(video::EMF_LIGHTING, false);

terrain->setMaterialTexture(0, driver->getTexture("../../media/terrain-texture.jpg"));
terrain->setMaterialTexture(1, driver->getTexture("../../media/detailmap3.jpg"));

terrain->setMaterialType(video::EMT_DETAIL_MAP);
terrain->scaleTexture(1.0f, 20.0f);
-

To be able to do collision with the terrain, we create a triangle - selector. If you want to know what triangle selectors do, just take - a look into the collision tutorial. The terrain triangle selector - works together with the terrain. To demonstrate this, we create a - collision response animator and attach it to the camera, so that the - camera will not be able to fly through the terrain.

-
- - - - -
// create triangle selector for the terrain	
scene::ITriangleSelector* selector =
smgr->createTerrainTriangleSelector(terrain, 0);
terrain->setTriangleSelector(selector);

// create collision response animator and attach it to the camera
scene::ISceneNodeAnimator* anim = smgr->createCollisionResponseAnimator(
selector, camera, core::vector3df(60,100,60),
core::vector3df(0,0,0),
core::vector3df(0,50,0));
selector->drop();
camera->addAnimator(anim);
anim->drop();
-

To make the user be able to switch between normal and wireframe mode, - we create an instance of the event reciever from above and let Irrlicht - know about it. In addition, we add the skybox which we already used - in lots of Irrlicht examples.

- - - - -
// create event receiver
MyEventReceiver receiver(terrain);
device->setEventReceiver(&receiver);

// create skybox
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);

smgr->addSkyBoxSceneNode(
driver->getTexture("../../media/irrlicht2_up.jpg"),
driver->getTexture("../../media/irrlicht2_dn.jpg"),
driver->getTexture("../../media/irrlicht2_lf.jpg"),
driver->getTexture("../../media/irrlicht2_rt.jpg"),
driver->getTexture("../../media/irrlicht2_ft.jpg"),
driver->getTexture("../../media/irrlicht2_bk.jpg"));
-driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, true);
-

That's it, draw everything. Now you know how to use terrain in Irrlicht.

- - - - -
	int lastFPS = -1;

while(device->run())
if (device->isWindowActive())
{
driver->beginScene(true, true, 0 );

smgr->drawAll();
env->drawAll();

driver->endScene(); -
// display frames per second in window title - int fps = driver->getFPS();

if (lastFPS != fps)
{
core::stringw str = L"Terrain Renderer - Irrlicht Engine [";
str += driver->getName();
str += "] FPS:";
str += fps;
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}

device->drop();

return 0;
}
-

 

-
-
-

 

- - + + + + + + + +Tutorial 12: Terrain Rendering + + + + +
+ + + +
+
+
+
Tutorial 12: Terrain Rendering
+
+
+
+012shot.jpg +
+

This tutorial will briefly show how to use the terrain renderer of Irrlicht. It will also show the terrain renderer triangle selector to be able to do collision detection with terrain.

+

Note that the Terrain Renderer in Irrlicht is based on Spintz' GeoMipMapSceneNode, lots of thanks go to him. DeusXL provided a new elegant simple solution for building larger area on small heightmaps -> terrain smoothing.

+

In the beginning there is nothing special. We include the needed header files and create an event listener to listen if the user presses certain keys.

#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
class MyEventReceiver : public IEventReceiver
{
public:
MyEventReceiver(scene::ISceneNode* terrain, scene::ISceneNode* skybox, scene::ISceneNode* skydome) :
Terrain(terrain), Skybox(skybox), Skydome(skydome), showBox(true), showDebug(false)
{
Skybox->setVisible(showBox);
Skydome->setVisible(!showBox);
}
bool OnEvent(const SEvent& event)
{
// check if user presses the key 'W' or 'D'
if (event.EventType == irr::EET_KEY_INPUT_EVENT && !event.KeyInput.PressedDown)
{
switch (event.KeyInput.Key)
{
case irr::KEY_KEY_W: // switch wire frame mode
Terrain->setMaterialFlag(video::EMF_WIREFRAME,
!Terrain->getMaterial(0).Wireframe);
Terrain->setMaterialFlag(video::EMF_POINTCLOUD, false);
return true;
case irr::KEY_KEY_P: // switch point cloud mode
Terrain->setMaterialFlag(video::EMF_POINTCLOUD,
!Terrain->getMaterial(0).PointCloud);
Terrain->setMaterialFlag(video::EMF_WIREFRAME, false);
return true;
case irr::KEY_KEY_D: // toggle detail map
Terrain->setMaterialType(
Terrain->getMaterial(0).MaterialType == video::EMT_SOLID ?
video::EMT_DETAIL_MAP : video::EMT_SOLID);
return true;
case irr::KEY_KEY_S: // toggle skies
showBox=!showBox;
Skybox->setVisible(showBox);
Skydome->setVisible(!showBox);
return true;
case irr::KEY_KEY_X: // toggle debug information
showDebug=!showDebug;
Terrain->setDebugDataVisible(showDebug?scene::EDS_BBOX_ALL:scene::EDS_OFF);
return true;
default:
break;
}
}
return false;
}
private:
scene::ISceneNode* Terrain;
scene::ISceneNode* Skybox;
scene::ISceneNode* Skydome;
bool showBox;
bool showDebug;
};

The start of the main function starts like in most other example. We ask the user for the desired renderer and start it up. This time with the advanced parameter handling.

int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// create device with full flexibility over creation parameters
// you can add more parameters if desired, check irr::SIrrlichtCreationParameters
irr::SIrrlichtCreationParameters params;
params.DriverType=driverType;
params.WindowSize=core::dimension2d<u32>(640, 480);
IrrlichtDevice* device = createDeviceEx(params);
if (device == 0)
return 1; // could not create selected driver.

First, we add standard stuff to the scene: A nice irrlicht engine logo, a small help text, a user controlled camera, and we disable the mouse cursor.

video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* env = device->getGUIEnvironment();
driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true);
const io::path mediaPath = getExampleMediaPath();
// add irrlicht logo
env->addImage(driver->getTexture(mediaPath + "irrlichtlogo3.png"),
core::position2d<s32>(10,10));
//set other font
env->getSkin()->setFont(env->getFont(mediaPath + "fontlucida.png"));
// add some help text (let's ignore 'P' and 'X' which are more about debugging)
env->addStaticText(
L"Press 'W' to change wireframe mode\nPress 'D' to toggle detail map\nPress 'S' to toggle skybox/skydome",
core::rect<s32>(10,421,250,475), true, true, 0, -1, true);
// add camera
scene::ICameraSceneNode* camera =
smgr->addCameraSceneNodeFPS(0,100.0f,1.2f);
camera->setPosition(core::vector3df(2700*2,255*2,2600*2));
camera->setTarget(core::vector3df(2397*2,343*2,2700*2));
camera->setFarValue(42000.0f);
// disable mouse cursor
device->getCursorControl()->setVisible(false);

Here comes the terrain renderer scene node: We add it just like any other scene node to the scene using ISceneManager::addTerrainSceneNode(). The first parameter is a file name to the heightmap we use. A heightmap is simply a gray scale texture. The terrain renderer loads it and creates the 3D terrain from it.

+

To make the terrain look bigger, we change it's scale factor to (40, 4.4, 40). Because we don't have any dynamic lights in the scene, we switch off the lighting, and we set the file terrain-texture.jpg as texture for the terrain and detailmap3.jpg as second texture, called detail map. At last, we set the scale values for the texture: The first texture will be repeated only one time over the whole terrain, and the second one (detail map) 20 times.

// add terrain scene node
scene::ITerrainSceneNode* terrain = smgr->addTerrainSceneNode(
mediaPath + "terrain-heightmap.bmp",
0, // parent node
-1, // node id
core::vector3df(0.f, 0.f, 0.f), // position
core::vector3df(0.f, 0.f, 0.f), // rotation
core::vector3df(40.f, 4.4f, 40.f), // scale
video::SColor ( 255, 255, 255, 255 ), // vertexColor
5, // maxLOD
scene::ETPS_17, // patchSize
4 // smoothFactor
);
terrain->setMaterialFlag(video::EMF_LIGHTING, false);
terrain->setMaterialTexture(0,
driver->getTexture(mediaPath + "terrain-texture.jpg"));
terrain->setMaterialTexture(1,
driver->getTexture(mediaPath + "detailmap3.jpg"));
terrain->setMaterialType(video::EMT_DETAIL_MAP);
terrain->scaleTexture(1.0f, 20.0f);

To be able to do collision with the terrain, we create a triangle selector. If you want to know what triangle selectors do, just take a look into the collision tutorial. The terrain triangle selector works together with the terrain. To demonstrate this, we create a collision response animator and attach it to the camera, so that the camera will not be able to fly through the terrain.

// create triangle selector for the terrain
scene::ITriangleSelector* selector
= smgr->createTerrainTriangleSelector(terrain, 0);
terrain->setTriangleSelector(selector);
// create collision response animator and attach it to the camera
scene::ISceneNodeAnimator* anim = smgr->createCollisionResponseAnimator(
selector, camera, core::vector3df(60,100,60),
core::vector3df(0,0,0),
core::vector3df(0,50,0));
selector->drop();
camera->addAnimator(anim);
anim->drop();

If you need access to the terrain data you can also do this directly via the following code fragment.

scene::CDynamicMeshBuffer* buffer = new scene::CDynamicMeshBuffer(video::EVT_2TCOORDS, video::EIT_16BIT);
terrain->getMeshBufferForLOD(*buffer, 0);
video::S3DVertex2TCoords* data = (video::S3DVertex2TCoords*)buffer->getVertexBuffer().getData();
// Work on data or get the IndexBuffer with a similar call.
(void)data; // disable unused variable warnings
buffer->drop(); // When done drop the buffer again.

To make the user be able to switch between normal and wireframe mode, we create an instance of the event receiver from above and let Irrlicht know about it. In addition, we add the skybox which we already used in lots of Irrlicht examples and a skydome, which is shown mutually exclusive with the skybox by pressing 'S'.

// create skybox and skydome
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
scene::ISceneNode* skybox=smgr->addSkyBoxSceneNode(
driver->getTexture(mediaPath + "irrlicht2_up.jpg"),
driver->getTexture(mediaPath + "irrlicht2_dn.jpg"),
driver->getTexture(mediaPath + "irrlicht2_lf.jpg"),
driver->getTexture(mediaPath + "irrlicht2_rt.jpg"),
driver->getTexture(mediaPath + "irrlicht2_ft.jpg"),
driver->getTexture(mediaPath + "irrlicht2_bk.jpg"));
scene::ISceneNode* skydome=smgr->addSkyDomeSceneNode(driver->getTexture(mediaPath + "skydome.jpg"),16,8,0.95f,2.0f);
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, true);
// create event receiver
MyEventReceiver receiver(terrain, skybox, skydome);
device->setEventReceiver(&receiver);

That's it, draw everything.

int lastFPS = -1;
while(device->run())
if (device->isWindowActive())
{
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(0));
smgr->drawAll();
env->drawAll();
driver->endScene();
// display frames per second in window title
int fps = driver->getFPS();
if (lastFPS != fps)
{
core::stringw str = L"Terrain Renderer - Irrlicht Engine [";
str += driver->getName();
str += "] FPS:";
str += fps;
// Also print terrain height of current camera position
// We can use camera position because terrain is located at coordinate origin
str += " Height: ";
str += terrain->getHeight(camera->getAbsolutePosition().X,
camera->getAbsolutePosition().Z);
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}
device->drop();
return 0;
}

Now you know how to use terrain in Irrlicht.

+
+ + +

 

+ + diff --git a/examples/13.RenderToTexture/tutorial.html b/examples/13.RenderToTexture/tutorial.html index 26340a3e..0b4c4351 100644 --- a/examples/13.RenderToTexture/tutorial.html +++ b/examples/13.RenderToTexture/tutorial.html @@ -1,244 +1,209 @@ - - -Irrlicht Engine Tutorial - - - - -
- - - - - - - - -
-
-
Tutorial 13. Render to Texture
-
-
-
-

This tutorial shows how to render to a texture using Irrlicht. Render - to texture is a feature with which it is possible to create nice special - effects. In addition, this tutorial shows how to enable specular highlights.

-

The program which is described here will look like this:

-


-

-
-
-
- - - - - - - -
Lets start!
-
-

In the beginning, everything as usual. Include the needed headers, - ask the user for the rendering driver, create the Irrlicht Device:

- - - - -
#include <irrlicht.h>
-#include <iostream>
-
-using namespace irr;
-
-#pragma comment(lib, "Irrlicht.lib")
-
-int main()
-{
-	// let user select driver type
-
-	video::E_DRIVER_TYPE driverType = video::EDT_DIRECT3D9;
-
-	printf("Please select the driver you want for this example:\n"\
" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
" (d) Software Renderer\n (e) Apfelbaum Software Renderer\n"\
" (f) NullDevice\n (otherKey) exit\n\n"); - - char i; - std::cin >> i; - - switch(i)
{
case 'a': driverType = video::EDT_DIRECT3D9;break;
case 'b': driverType = video::EDT_DIRECT3D8;break;
case 'c': driverType = video::EDT_OPENGL; break;
case 'd': driverType = video::EDT_SOFTWARE; break;
case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
case 'f': driverType = video::EDT_NULL; break;
default: return 1;
} - - // create device and exit if creation failed - - IrrlichtDevice *device = - createDevice(driverType, core::dimension2d(640, 480), - 16, false, false); - - if (device == 0) - return 1; // could not create selected driver. - - video::IVideoDriver* driver = device->getVideoDriver(); - scene::ISceneManager* smgr = device->getSceneManager(); - gui::IGUIEnvironment* env = device->getGUIEnvironment();
-

Now, we load an animated mesh to be displayed. As in most examples, - we'll take the fairy md2 model. The difference here: We set the shininess
- of the model to a value other than 0 which is the default value. This - enables specular highlights on the model if dynamic lighting is on. - The value influences the size of the highlights.

- - - - -
// load and display animated fairy mesh
-
-	scene::IAnimatedMeshSceneNode* fairy = smgr->addAnimatedMeshSceneNode(
-		smgr->getMesh("../../media/faerie.md2"));
-
-	if (fairy)
-	{
-		fairy->setMaterialTexture(0, driver->getTexture("../../media/faerie2.bmp")); // set diffuse texture
-		fairy->setMaterialFlag(video::EMF_LIGHTING, true); // enable dynamic lighting
-		fairy->getMaterial(0).Shininess = 20.0f; // set size of specular highlights
-		fairy->setPosition(core::vector3df(-10,0,-100));
-	}
-

To make specular highlights appear on the model, we need a dynamic - light in the scene. We add one directly in vicinity of the model. - In addition, to make the model not that dark, we set the ambient light - to gray.

- - - - -
-	// add white light
-	scene::ILightSceneNode* light = smgr->addLightSceneNode(0,
-		core::vector3df(-15,5,-105), video::SColorf(1.0f, 1.0f, 1.0f));
-
-	// set ambient light
-	driver->setAmbientLight(video::SColor(0,60,60,60));
-

The next is just some standard stuff: Add a user controlled camera - to the scene, disable mouse cursor, and add a test cube and let it - rotate to make the scene more interesting.

- - - - -
-	// add fps camera
-	scene::ICameraSceneNode* fpsCamera = smgr->addCameraSceneNodeFPS();
-	fpsCamera->setPosition(core::vector3df(-50,50,-150));
-
-	// disable mouse cursor
-	device->getCursorControl()->setVisible(false);
-
-	// create test cube
-	scene::ISceneNode* test = smgr->addCubeSceneNode(60);
-
-	// let the cube rotate and set some light settings
-	scene::ISceneNodeAnimator* anim = smgr->createRotationAnimator(
-		core::vector3df(0.3f, 0.3f,0));
-
-	test->setPosition(core::vector3df(-100,0,-100));
-	test->setMaterialFlag(video::EMF_LIGHTING, false); // disable dynamic lighting
-	test->addAnimator(anim);
-	anim->drop();
-
-	// set window caption
-	device->setWindowCaption(L"Irrlicht Engine - Render to Texture and Specular Highlights example");
-

To test out the render to texture feature, we need a render target - texture. These are not like standard textures, but need to be created - first. To create one, we call IVideoDriver::createRenderTargetTexture() - and specify the size of the texture. Please don't use sizes bigger - than the frame buffer for this, because the render target shares the - zbuffer with the frame buffer. And because we want to render the scene - not from the user camera into the texture, we add another, fixed camera - to the scene. But before we do all this, we check if the current running - driver is able to render to textures. If it is not, we simply display - a warning text.

- - - - -
// create render target
-	video::ITexture* rt = 0;
-	scene::ICameraSceneNode* fixedCam = 0;
-	
-
-	if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET))
-	{
-		rt = driver->createRenderTargetTexture(core::dimension2d(256,256));
-		test->setMaterialTexture(0, rt); // set material of cube to render target
-
-		// add fixed camera
-		fixedCam = smgr->addCameraSceneNode(0, core::vector3df(10,10,-80),
-			core::vector3df(-10,10,-100));
-	}
-	else
-	{
-		// create problem text
-		gui::IGUISkin* skin = env->getSkin();
-		gui::IGUIFont* font = env->getFont("../../media/fonthaettenschweiler.bmp");
-		if (font)
-			skin->setFont(font);
-
-		gui::IGUIStaticText* text = env->addStaticText(
-			L"Your hardware or this renderer is not able to use the "\
-			L"render to texture feature. RTT Disabled.",
-			core::rect(150,20,470,60));
-
-		text->setOverrideColor(video::SColor(100,255,255,255));
-	}
-

Nearly finished. Now we need to draw everything. Every frame, we - draw the scene twice. Once from the fixed camera into the render target - texture and once as usual. When rendering into the render target, - we need to disable the visibilty of the test cube, because it has - the render target texture applied to it.
- That's, wasn't quite complicated I hope. :)

- - - - -
while(device->run())
-	if (device->isWindowActive())
-	{
-		driver->beginScene(true, true, 0);
-
-		if (rt)
-		{
-			// draw scene into render target
-			
-			// set render target texture
-			driver->setRenderTarget(rt, true, true, video::SColor(0,0,0,255));     
-
-			// make cube invisible and set fixed camera as active camera
-			test->setVisible(false);
-			smgr->setActiveCamera(fixedCam);
-
-			// draw whole scene into render buffer
-			smgr->drawAll();                 
-
-			// set back old render target
-			driver->setRenderTarget(0);      
-
-			// make the cube visible and set the user controlled camera as active one
-			test->setVisible(true);
-			smgr->setActiveCamera(fpsCamera);
-		}
-		
-		// draw scene normally
-		smgr->drawAll(); 
-		env->drawAll();
-
-		driver->endScene();
-	}
-
-	if (rt)
-		rt->drop(); // drop render target because we created if with a create() method
-
-	device->drop(); // drop device
-	return 0;
-}
-
- -

 

-
-
-

 

- - + + + + + + + +Tutorial 13: Render To Texture + + + + +
+ + + +
+
+
+
Tutorial 13: Render To Texture
+
+
+
+013shot.jpg +
+

This tutorial shows how to render to a texture using Irrlicht. Render to texture is a feature where everything which would usually be rendered to the screen is instead written to a (special) texture. This can be used to create nice special effects. In addition, this tutorial shows how to enable specular highlights.

+

In the beginning, everything as usual. Include the needed headers, ask the user for the rendering driver, create the Irrlicht device:

#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// create device and exit if creation failed
IrrlichtDevice *device =
createDevice(driverType, core::dimension2d<u32>(640, 480),
16, false, false);
if (device == 0)
return 1; // could not create selected driver.
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* env = device->getGUIEnvironment();
const io::path mediaPath = getExampleMediaPath();

Now, we load an animated mesh to be displayed. As in most examples, we'll take the fairy md2 model. The difference here: We set the shininess of the model to a value other than 0 which is the default value. This enables specular highlights on the model if dynamic lighting is on. The value influences the size of the highlights.

// load and display animated fairy mesh
scene::IAnimatedMeshSceneNode* fairy = smgr->addAnimatedMeshSceneNode(
smgr->getMesh(mediaPath + "faerie.md2"));
if (fairy)
{
fairy->setMaterialTexture(0,
driver->getTexture(mediaPath + "faerie2.bmp")); // set diffuse texture
fairy->setMaterialFlag(video::EMF_LIGHTING, true); // enable dynamic lighting
fairy->getMaterial(0).Shininess = 20.0f; // set size of specular highlights
fairy->setPosition(core::vector3df(-10,0,-100));
fairy->setMD2Animation ( scene::EMAT_STAND );
}

To make specular highlights appear on the model, we need a dynamic light in the scene. We add one directly in vicinity of the model. In addition, to make the model not that dark, we set the ambient light to gray.

// add white light
smgr->addLightSceneNode(0, core::vector3df(-15,5,-105),
video::SColorf(1.0f, 1.0f, 1.0f));
// set ambient light
smgr->setAmbientLight(video::SColor(0,60,60,60));

The next is just some standard stuff: Add a test cube and let it rotate to make the scene more interesting. The user defined camera and cursor setup is made later on, right before the render loop.

// create test cube
scene::ISceneNode* cube = smgr->addCubeSceneNode(60);
// let the cube rotate and set some light settings
scene::ISceneNodeAnimator* anim = smgr->createRotationAnimator(
core::vector3df(0.3f, 0.3f,0));
cube->setPosition(core::vector3df(-100,0,-100));
cube->setMaterialFlag(video::EMF_LIGHTING, false); // disable dynamic lighting
cube->addAnimator(anim);
anim->drop();
// set window caption
device->setWindowCaption(L"Irrlicht Engine - Render to Texture and Specular Highlights example");

To test out the render to texture feature, we need to define our new rendertarget. The rendertarget will need one texture to receive the result you would otherwise see on screen and one texture which is used as depth-buffer.

+

(Note: If you worked with older Irrlicht versions (before 1.9) you might be used to only create a rendertarget texture and no explicit rendertarget. While that's still possible, it's no longer recommended.)

+

The rendertarget textures are not like standard textures, but need to be created first. To create them, we call IVideoDriver::addRenderTargetTexture() and specify the size of the texture and the type. For depth-maps you can use types ECF_D16, ECF_D32 or ECF_D24S8. When ECF_D24S8 you can also use a stencil-buffer.

+

Because we want to render the scene not from the user camera into the texture, we add another fixed camera to the scene. But before we do all this, we check if the current running driver is able to render to textures. If it is not, we simply display a warning text.

// create render target
video::IRenderTarget* renderTarget = 0;
scene::ICameraSceneNode* fixedCam = 0;
if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET))
{
const core::dimension2d<u32> rtDim(256, 256); // always use same size for render target texture and it's depth-buffer
video::ITexture* renderTargetTex = driver->addRenderTargetTexture(rtDim, "RTT1", video::ECF_A8R8G8B8);
video::ITexture* renderTargetDepth = driver->addRenderTargetTexture(rtDim, "DepthStencil", video::ECF_D16);
renderTarget = driver->addRenderTarget();
renderTarget->setTexture(renderTargetTex, renderTargetDepth);
cube->setMaterialTexture(0, renderTargetTex); // set material of cube to render target
// add fixed camera
fixedCam = smgr->addCameraSceneNode(0, core::vector3df(10,10,-80),
core::vector3df(-10,10,-100));
}
else
{
// create problem text
gui::IGUISkin* skin = env->getSkin();
gui::IGUIFont* font = env->getFont(mediaPath + "fonthaettenschweiler.bmp");
if (font)
skin->setFont(font);
gui::IGUIStaticText* text = env->addStaticText(
L"Your hardware or this renderer is not able to use the "\
L"render to texture feature. RTT Disabled.",
core::rect<s32>(150,20,470,60));
text->setOverrideColor(video::SColor(100,255,255,255));
}
// add fps camera
scene::ICameraSceneNode* fpsCamera = smgr->addCameraSceneNodeFPS();
fpsCamera->setPosition(core::vector3df(-50,50,-150));
// disable mouse cursor
device->getCursorControl()->setVisible(false);

Nearly finished. Now we need to draw everything. Every frame, we draw the scene twice. Once from the fixed camera into the render target texture and once as usual. When rendering into the render target, we need to disable the visibility of the test cube, because it has the render target texture applied to it. That's it, wasn't too complicated I hope. :)

int lastFPS = -1;
while(device->run())
if (device->isWindowActive())
{
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(0));
if (renderTarget)
{
// draw scene into render target
// set render target
driver->setRenderTargetEx(renderTarget, video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(0,0,0,255));
// make cube invisible and set fixed camera as active camera
cube->setVisible(false);
smgr->setActiveCamera(fixedCam);
// draw whole scene into render buffer
smgr->drawAll();
// set back old render target (the screen)
driver->setRenderTargetEx(0, 0);
// make the cube visible and set the user controlled camera as active one
cube->setVisible(true);
smgr->setActiveCamera(fpsCamera);
}
// draw scene normally
smgr->drawAll();
env->drawAll();
driver->endScene();
// display frames per second in window title
int fps = driver->getFPS();
if (lastFPS != fps)
{
core::stringw str = L"Irrlicht Engine - Render to Texture and Specular Highlights example";
str += " FPS:";
str += fps;
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}
device->drop(); // drop device
return 0;
}
+ + +

 

+ + diff --git a/examples/14.Win32Window/tutorial.html b/examples/14.Win32Window/tutorial.html index 6c5d5baa..849c474f 100644 --- a/examples/14.Win32Window/tutorial.html +++ b/examples/14.Win32Window/tutorial.html @@ -1,247 +1,210 @@ - - -Irrlicht Engine Tutorial - - - - -
- - - - - - - - -
-
-
Tutorial 14. Win32 Window
-
-
-
-

This example only runs in Windows and demonstrates that Irrlicht can - run inside a win32 window. MFC and .NET Windows.Forms windows are possible - too.

-

The program which is described here will look like this:

-


-

-
-
-
- - - - - - - -
Lets start!
-
-

In the begining, we create a windows window using the windows API. - I'm not going to explain this code, because it is windows specific. - See the MSDN or a windows book for details.

- - - - -
#include <irrlicht.h>
-#include <windows.h> // this example only runs with windows
-
-using namespace irr;
-
-#pragma comment(lib, "irrlicht.lib")
-
-HWND hOKButton;
-HWND hWnd;
-
-static LRESULT CALLBACK CustomWndProc(HWND hWnd, UINT message,
-    WPARAM wParam, LPARAM lParam)
-{
-	switch (message) 
-	{
-	case WM_COMMAND:
-		{
-			HWND hwndCtl = (HWND)lParam;
-			int code = HIWORD(wParam);
-
-			if (hwndCtl == hOKButton)
-			{
-				DestroyWindow(hWnd);
-				PostQuitMessage(0);
-				return 0;
-			}		
-		}
-		break;
-	case WM_DESTROY:
-		PostQuitMessage(0);
-		return 0;
-
-	}
-
-	return DefWindowProc(hWnd, message, wParam, lParam);
-}
-
-int main()
-//int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hpre, LPSTR cmd, int cc)
-{
-	HINSTANCE hInstance = 0;
-	// create dialog
-
-	const char* Win32ClassName = "CIrrlichtWindowsTestDialog";
-
-	WNDCLASSEX wcex;
-	wcex.cbSize			= sizeof(WNDCLASSEX); 
-	wcex.style			= CS_HREDRAW | CS_VREDRAW;
-	wcex.lpfnWndProc	= (WNDPROC)CustomWndProc;
-	wcex.cbClsExtra		= 0;
-	wcex.cbWndExtra		= DLGWINDOWEXTRA; 
-	wcex.hInstance		= hInstance;
-	wcex.hIcon			= NULL;
-	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
-	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW);
-	wcex.lpszMenuName	= 0;
-	wcex.lpszClassName	= Win32ClassName;
-	wcex.hIconSm		= 0;
-
-	RegisterClassEx(&wcex);
-
-	DWORD style = WS_SYSMENU | WS_BORDER | WS_CAPTION | 
-		WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_MAXIMIZEBOX
-		| WS_MINIMIZEBOX | WS_SIZEBOX;
-
-	int windowWidth = 440;
-	int windowHeight = 380;
-
-	hWnd = CreateWindow( Win32ClassName, "Irrlicht Win32 window example",
-		style, 100, 100, windowWidth, windowHeight,
-		NULL, NULL, hInstance, NULL);
-
-	RECT clientRect;
-	GetClientRect(hWnd, &clientRect);
-	windowWidth = clientRect.right;
-	windowHeight = clientRect.bottom;
-
-	// create ok button
-
-	hOKButton = CreateWindow(
-	    "BUTTON", "OK - Close", WS_CHILD | WS_VISIBLE | BS_TEXT, 
-		windowWidth - 160, windowHeight - 40, 150, 30, hWnd, NULL, 
-		hInstance, NULL);
-
-	// create some text
-	
-	CreateWindow("STATIC", 
-        "This is Irrlicht running inside a standard Win32 window.\n"\
-		"Also mixing with MFC and .NET Windows.Forms is possible.",
-		WS_CHILD | WS_VISIBLE, 20, 20, 400, 40, hWnd, NULL, hInstance, NULL);
-
-	// create window to put irrlicht in
-
-	HWND hIrrlichtWindow =
CreateWindow("BUTTON", "", WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, - 50, 80, 320, 220, hWnd, NULL, hInstance, NULL); - -
-

So now that we have some window, we can create an Irrlicht device - inside of it. We use Irrlicht createEx() function for this. We only - need the handle (HWND) to that window, set it as windowsID parameter - and start up the engine as usual. That's it.

- - - - -
	// create irrlicht device in the button window
-
-	irr::SIrrlichtCreationParameters param;
-	param.WindowId = reinterpret_cast(hIrrlichtWindow); // hColorButton
-	param.DriverType = video::EDT_OPENGL;
-
-	irr::IrrlichtDevice* device = irr::createDeviceEx(param);
-	
-	// setup a simple 3d scene
-
-	irr::scene::ISceneManager* smgr = device->getSceneManager();
-	video::IVideoDriver* driver = device->getVideoDriver();
-
-	scene::ICameraSceneNode* cam = smgr->addCameraSceneNode();
-	cam->setTarget(core::vector3df(0,0,0));
-
-	scene::ISceneNodeAnimator* anim =
-	   smgr->createFlyCircleAnimator(core::vector3df(0,10,0), 30.0f);
-	cam->addAnimator(anim);
-	anim->drop();
-
-	scene::ISceneNode* cube = smgr->addCubeSceneNode(25);
-	cube->setMaterialFlag(video::EMF_LIGHTING, false);
-	
-	cube->setMaterialTexture(0, driver->getTexture("../../media/rockwall.bmp"));
-
-	smgr->addSkyBoxSceneNode(
-	driver->getTexture("../../media/irrlicht2_up.jpg"),
-	driver->getTexture("../../media/irrlicht2_dn.jpg"),
-	driver->getTexture("../../media/irrlicht2_lf.jpg"),
-	driver->getTexture("../../media/irrlicht2_rt.jpg"),
-	driver->getTexture("../../media/irrlicht2_ft.jpg"),
-	driver->getTexture("../../media/irrlicht2_bk.jpg"));
-
-	// show and execute dialog
-
-	ShowWindow(hWnd , SW_SHOW);
-	UpdateWindow(hWnd);
-
-

Now the only thing missing is the drawing loop using IrrlichtDevice::run(). - We do this as usual. But instead of this, there is another possibility: - You can also simply use your own message loop using GetMessage, DispatchMessage - and whatever. Calling
- Device->run() will cause Irrlicht to dispatch messages internally - too. You need not call Device->run() if you want to do your own - message dispatching loop, but Irrlicht will not be able to fetch user - input then and you have to do it on your own using the window messages, - DirectInput, or whatever.

- - - - -
	while (device->run())
-	{
-		driver->beginScene(true, true, 0);
-		smgr->drawAll();
-		driver->endScene();
-	}
-
-	// the alternative, own message dispatching loop without Device->run() would
-	// look like this:
-
-	/*MSG msg;
-	while (true)
-	{
-		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
-		{
-			TranslateMessage(&msg);
-			DispatchMessage(&msg);
-
-			if (msg.message == WM_QUIT)
-				break;
-		}
-		
-		// advance virtual time
-		device->getTimer()->tick();
-
-		// draw engine picture
-		driver->beginScene(true, true, 0);
-		smgr->drawAll();
-		driver->endScene();
-	}*/
-
-	device->closeDevice();
-	device->drop();
-
-	return 0;
-}
-

That's it, Irrlicht now runs in your own windows window.

-
-
-
-

 

- - + + + + + + + +Tutorial 14: Win32 Window + + + + +
+ + + +
+
+
+
Tutorial 14: Win32 Window
+
+
+
+014shot.jpg +
+

This example only runs under MS Windows and demonstrates that Irrlicht can render inside a win32 window. MFC and .NET Windows.Forms windows are possible, too.

+

In the beginning, we create a windows window using the windows API. I'm not going to explain this code, because it is windows specific. See the MSDN or a windows book for details.

#include <irrlicht.h>
#ifndef _IRR_WINDOWS_
#error Windows only example
#else
#include <windows.h> // this example only runs with windows
#include <iostream>
#include "driverChoice.h"
#include "exampleHelper.h"
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "irrlicht.lib")
#endif
HWND hOKButton;
HWND hWnd;
static LRESULT CALLBACK CustomWndProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
HWND hwndCtl = (HWND)lParam;
int code = HIWORD(wParam);
if (hwndCtl == hOKButton)
{
DestroyWindow(hWnd);
PostQuitMessage(0);
return 0;
}
}
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}

Now ask for the driver and create the Windows specific window.

int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
printf("Select the render window (some dead window may exist too):\n"\
" (a) Window with button (via CreationParam)\n"\
" (b) Window with button (via beginScene)\n"\
" (c) Own Irrlicht window (default behavior)\n"\
" (otherKey) exit\n\n");
char key;
std::cin >> key;
if (key != 'a' && key != 'b' && key != 'c')
return 1;
HINSTANCE hInstance = 0;
// create dialog
const fschar_t* Win32ClassName = __TEXT("CIrrlichtWindowsTestDialog");
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)CustomWndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = DLGWINDOWEXTRA;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW);
wcex.lpszMenuName = 0;
wcex.lpszClassName = Win32ClassName;
wcex.hIconSm = 0;
RegisterClassEx(&wcex);
DWORD style = WS_SYSMENU | WS_BORDER | WS_CAPTION |
WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SIZEBOX;
int windowWidth = 440;
int windowHeight = 380;
hWnd = CreateWindow( Win32ClassName, __TEXT("Irrlicht Win32 window example"),
style, 100, 100, windowWidth, windowHeight,
NULL, NULL, hInstance, NULL);
RECT clientRect;
GetClientRect(hWnd, &clientRect);
windowWidth = clientRect.right;
windowHeight = clientRect.bottom;
// create ok button
hOKButton = CreateWindow(__TEXT("BUTTON"), __TEXT("OK - Close"), WS_CHILD | WS_VISIBLE | BS_TEXT,
windowWidth - 160, windowHeight - 40, 150, 30, hWnd, NULL, hInstance, NULL);
// create some text
CreateWindow(__TEXT("STATIC"), __TEXT("This is Irrlicht running inside a standard Win32 window.\n")\
__TEXT("Also mixing with MFC and .NET Windows.Forms is possible."),
WS_CHILD | WS_VISIBLE, 20, 20, 400, 40, hWnd, NULL, hInstance, NULL);
// create window to put irrlicht in
HWND hIrrlichtWindow = CreateWindow(__TEXT("BUTTON"), __TEXT(""),
WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
50, 80, 320, 220, hWnd, NULL, hInstance, NULL);
video::SExposedVideoData videodata((key=='b')?hIrrlichtWindow:0);

So now that we have some window, we can create an Irrlicht device inside of it. We use Irrlicht createEx() function for this. We only need the handle (HWND) to that window, set it as windowsID parameter and start up the engine as usual. That's it.

// create irrlicht device in the button window
irr::SIrrlichtCreationParameters param;
param.DriverType = driverType;
if (key=='a')
param.WindowId = reinterpret_cast<void*>(hIrrlichtWindow);
irr::IrrlichtDevice* device = irr::createDeviceEx(param);
// setup a simple 3d scene
irr::scene::ISceneManager* smgr = device->getSceneManager();
video::IVideoDriver* driver = device->getVideoDriver();
if (driverType==video::EDT_OPENGL)
{
HDC HDc=GetDC(hIrrlichtWindow);
PIXELFORMATDESCRIPTOR pfd={0};
pfd.nSize=sizeof(PIXELFORMATDESCRIPTOR);
int pf = GetPixelFormat(HDc);
DescribePixelFormat(HDc, pf, sizeof(PIXELFORMATDESCRIPTOR), &pfd);
pfd.dwFlags |= PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;
pfd.cDepthBits=16;
pf = ChoosePixelFormat(HDc, &pfd);
SetPixelFormat(HDc, pf, &pfd);
videodata.OpenGLWin32.HDc = HDc;
videodata.OpenGLWin32.HRc=wglCreateContext(HDc);
wglShareLists((HGLRC)driver->getExposedVideoData().OpenGLWin32.HRc, (HGLRC)videodata.OpenGLWin32.HRc);
}
scene::ICameraSceneNode* cam = smgr->addCameraSceneNode();
cam->setTarget(core::vector3df(0,0,0));
scene::ISceneNodeAnimator* anim =
smgr->createFlyCircleAnimator(core::vector3df(0,15,0), 30.0f);
cam->addAnimator(anim);
anim->drop();
scene::ISceneNode* cube = smgr->addCubeSceneNode(20);
const io::path mediaPath = getExampleMediaPath();
cube->setMaterialTexture(0, driver->getTexture(mediaPath + "wall.bmp"));
cube->setMaterialTexture(1, driver->getTexture(mediaPath + "water.jpg"));
cube->setMaterialFlag( video::EMF_LIGHTING, false );
cube->setMaterialType( video::EMT_REFLECTION_2_LAYER );
smgr->addSkyBoxSceneNode(
driver->getTexture(mediaPath + "irrlicht2_up.jpg"),
driver->getTexture(mediaPath + "irrlicht2_dn.jpg"),
driver->getTexture(mediaPath + "irrlicht2_lf.jpg"),
driver->getTexture(mediaPath + "irrlicht2_rt.jpg"),
driver->getTexture(mediaPath + "irrlicht2_ft.jpg"),
driver->getTexture(mediaPath + "irrlicht2_bk.jpg"));
// This shows that we can render to multiple windows within one application
device->getGUIEnvironment()->addStaticText(core::stringw("Second screen render").c_str(),core::recti(0,0,200,200));
// show and execute dialog
ShowWindow(hWnd , SW_SHOW);
UpdateWindow(hWnd);
// do message queue

Now the only thing missing is the drawing loop using IrrlichtDevice::run(). We do this as usual. But instead of this, there is another possibility: You can also simply use your own message loop using GetMessage, DispatchMessage and whatever. Calling Device->run() will cause Irrlicht to dispatch messages internally too. You need not call Device->run() if you want to do your own message dispatching loop, but Irrlicht will not be able to fetch user input then and you have to do it on your own using the window messages, DirectInput, or whatever.

while (device->run())
{
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(0), 1.f, 0, videodata);
smgr->drawAll();
driver->endScene();
if (key=='b')
{
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(0xbbbbbbbb));
device->getGUIEnvironment()->drawAll();
driver->endScene();
}
}

The alternative, own message dispatching loop without Device->run() would look like this:

MSG msg; while (true) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg);

+

if (msg.message == WM_QUIT) break; }

+

advance virtual time device->getTimer()->tick();

+

draw engine picture driver->beginScene(true, true, 0, (key=='c')?hIrrlichtWindow:0); smgr->drawAll(); driver->endScene(); }

device->closeDevice();
device->drop();
return 0;
}
#endif // if windows

That's it, Irrlicht now runs in your own windows window.

+
+ + +

 

+ + diff --git a/examples/15.LoadIrrFile/tutorial.html b/examples/15.LoadIrrFile/tutorial.html index e3e4a6b2..3416c8f2 100644 --- a/examples/15.LoadIrrFile/tutorial.html +++ b/examples/15.LoadIrrFile/tutorial.html @@ -1,129 +1,210 @@ - - -Irrlicht Engine Tutorial - - - - -
- - - - - - - - -
- Tutorial 15. Load .irr File
-

Since version 1.1, Irrlicht is able to save and load the full scene - graph into an .irr file, an xml based format. There is also an editor - available to edit those files, named irrEdit on http://www.ambiera.com/irredit, - which can also be used as world and particle editor. This tutorial shows - how to use .irr files.

-


-

-
-
- - - - - - - -
- Lets start!
-

Lets start: Create an Irrlicht device and setup the window.

- - - - -
#include 
-#include 
-using namespace irr;
-
-#pragma comment(lib, "Irrlicht.lib")
-
-int main()
-{
-	// ask user for driver
-
-	video::E_DRIVER_TYPE driverType;
-
-	printf("Please select the driver you want for this example:\n"\
-		" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
-		" (d) Software Renderer\n (e) Apfelbaum Software Renderer\n"\
-		" (f) NullDevice\n (otherKey) exit\n\n");
-
-	char i;
-	std::cin >> i;
-
-	switch(i)
-	{
-		case 'a': driverType = video::EDT_DIRECT3D9;break;
-		case 'b': driverType = video::EDT_DIRECT3D8;break;
-		case 'c': driverType = video::EDT_OPENGL;   break;
-		case 'd': driverType = video::EDT_SOFTWARE; break;
-		case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
-		case 'f': driverType = video::EDT_NULL;     break;
-		default: return 1;
-	}	
-
-	// create device and exit if creation failed
-
-	IrrlichtDevice* device =
-		createDevice(driverType, core::dimension2d(640, 480));
-
-	if (device == 0)
-		return 1; // could not create selected driver.
-
-	device->setWindowCaption(L"Load .irr file example");
-
-	video::IVideoDriver* driver = device->getVideoDriver();
-	scene::ISceneManager* smgr = device->getSceneManager();
-
-

Now load our .irr file. .irr files can store the whole scene graph - including animators, materials and particle systems. And there is also - the possibility to store arbitrary user data for every scene node in - that file. To keep this example simple, we are simply loading the scene - here. See the documentation at ISceneManager::loadScene and ISceneManager::saveScene - for more information. So to load and display a complicated huge scene, - we only need a single call to loadScene().

- - - - -
// load the scene
smgr->loadScene("../../media/example.irr");
-

That was it already. Now add a camera and draw the scene.

- - - - -
	// add a user controlled camera
-
-	smgr->addCameraSceneNodeFPS();
-
-	// and draw everything.
-	
-	while(device->run())
-	if (device->isWindowActive())
-	{
-		driver->beginScene(true, true, video::SColor(0,200,200,200));
-		smgr->drawAll();
-		driver->endScene();
-	}
-
-	device->drop();
-	
-	return 0;
-}
-
- -
-

 

-
-

 

-

 

- - + + + + + + + +Tutorial 15: Loading Scenes from .irr Files + + + + +
+ + + +
+
+
+
Tutorial 15: Loading Scenes from .irr Files
+
+
+
+015shot.jpg +
+

Since version 1.1, Irrlicht is able to save and load the full scene graph into an .irr file, an xml based format. There is an editor available to edit those files, named irrEdit (http://www.ambiera.com/irredit) which can also be used as world and particle editor. This tutorial shows how to use .irr files.

+

Lets start: Create an Irrlicht device and setup the window.

#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
int main(int argc, char** argv)
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// create device and exit if creation failed
IrrlichtDevice* device =
createDevice(driverType, core::dimension2d<u32>(640, 480));
if (device == 0)
return 1; // could not create selected driver.
device->setWindowCaption(L"Load .irr file example");
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();

Now load our .irr file. .irr files can store the whole scene graph including animators, materials and particle systems. And there is also the possibility to store arbitrary user data for every scene node in that file. To keep this example simple, we are simply loading the scene here. See the documentation at ISceneManager::loadScene and ISceneManager::saveScene for more information. So to load and display a complicated huge scene, we only need a single call to loadScene().

// load the scene

You might have to work around some minor problems in current .irr loader:

    +
  • It can't load meshes relative to the .irr file, but only relative to the working directory. So you might have to change your working directory to the path where the .irr file is in.
  • +
  • When passing a custom parent node to loadScene then irr_scene attributes will be passed to that. Usually not a problem, but for example AmbientLight will not change that way unless you create a custom SceneNode type which can interpret those attributes.
    if (argc>1)
    smgr->loadScene(argv[1]);
    else
    smgr->loadScene(getExampleMediaPath() + "example.irr");
    Now we'll create a camera, and give it a collision response animator that's built from the mesh nodes in the scene we just loaded.
    scene::ICameraSceneNode * camera = smgr->addCameraSceneNodeFPS(0, 50.f, 0.1f);
    // Create a meta triangle selector to hold several triangle selectors.
    scene::IMetaTriangleSelector * meta = smgr->createMetaTriangleSelector();
    Now we will find all the nodes in the scene and create triangle selectors for all suitable nodes. Typically, you would want to make a more informed decision about which nodes to performs collision checks on; you could capture that information in the node name or Id.
    core::array<scene::ISceneNode *> nodes;
    smgr->getSceneNodesFromType(scene::ESNT_ANY, nodes); // Find all nodes
    for (u32 i=0; i < nodes.size(); ++i)
    {
    scene::ISceneNode * node = nodes[i];
    scene::ITriangleSelector * selector = 0;
    switch(node->getType())
    {
    case scene::ESNT_CUBE:
    case scene::ESNT_ANIMATED_MESH:
    // Because the selector won't animate with the mesh,
    // and is only being used for camera collision, we'll just use an approximate
    // bounding box instead of ((scene::IAnimatedMeshSceneNode*)node)->getMesh(0)
    selector = smgr->createTriangleSelectorFromBoundingBox(node);
    break;
    case scene::ESNT_MESH:
    case scene::ESNT_SPHERE: // Derived from IMeshSceneNode
    selector = smgr->createTriangleSelector(((scene::IMeshSceneNode*)node)->getMesh(), node);
    break;
    case scene::ESNT_TERRAIN:
    selector = smgr->createTerrainTriangleSelector((scene::ITerrainSceneNode*)node);
    break;
    case scene::ESNT_OCTREE:
    selector = smgr->createOctreeTriangleSelector(((scene::IMeshSceneNode*)node)->getMesh(), node);
    break;
    default:
    // Don't create a selector for this node type
    break;
    }
    if(selector)
    {
    // Add it to the meta selector, which will take a reference to it
    meta->addTriangleSelector(selector);
    // And drop my reference to it, so that the meta selector owns it.
    selector->drop();
    }
    }
    Now that the mesh scene nodes have had triangle selectors created and added to the meta selector, create a collision response animator from that meta selector.
    scene::ISceneNodeAnimator* anim = smgr->createCollisionResponseAnimator(
    meta, camera, core::vector3df(5,5,5),
    core::vector3df(0,0,0));
    meta->drop(); // I'm done with the meta selector now
    camera->addAnimator(anim);
    anim->drop(); // I'm done with the animator now
    // And set the camera position so that it doesn't start off stuck in the geometry
    camera->setPosition(core::vector3df(0.f, 20.f, 0.f));
    // Point the camera at the cube node, by finding the first node of type ESNT_CUBE
    scene::ISceneNode * cube = smgr->getSceneNodeFromType(scene::ESNT_CUBE);
    if(cube)
    camera->setTarget(cube->getAbsolutePosition());
    That's it. Draw everything and finish as usual.
    int lastFPS = -1;
    while(device->run())
    if (device->isWindowActive())
    {
    driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(0,200,200,200));
    smgr->drawAll();
    driver->endScene();
    int fps = driver->getFPS();
    if (lastFPS != fps)
    {
    core::stringw str = L"Load Irrlicht File example - Irrlicht Engine [";
    str += driver->getName();
    str += "] FPS:";
    str += fps;
    device->setWindowCaption(str.c_str());
    lastFPS = fps;
    }
    }
    device->drop();
    return 0;
    }
  • +
+
+ + +

 

+ + diff --git a/examples/16.Quake3MapShader/tutorial.html b/examples/16.Quake3MapShader/tutorial.html new file mode 100644 index 00000000..6397cdf5 --- /dev/null +++ b/examples/16.Quake3MapShader/tutorial.html @@ -0,0 +1,206 @@ + + + + + + + +Tutorial 16: Quake3 Map Shader Support + + + + +
+ + + +
+
+
+
Tutorial 16: Quake3 Map Shader Support
+
+
+
+016shot.jpg +
+

This tutorial shows how to load a Quake 3 map into the engine, create a SceneNode for optimizing the speed of rendering and how to create a user controlled camera.

+

Lets start like the HelloWorld example: We include the irrlicht header files and an additional file to be able to ask the user for a driver type using the console.

#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"

define which Quake3 Level should be loaded

#define IRRLICHT_QUAKE3_ARENA
//#define ORIGINAL_QUAKE3_ARENA
//#define CUSTOM_QUAKE3_ARENA
//#define SHOW_SHADER_NAME
#ifdef ORIGINAL_QUAKE3_ARENA
#define QUAKE3_STORAGE_FORMAT addFolderFileArchive
#define QUAKE3_STORAGE_1 "/baseq3/"
#ifdef CUSTOM_QUAKE3_ARENA
#define QUAKE3_STORAGE_2 "/cf/"
#define QUAKE3_MAP_NAME "maps/cf.bsp"
#else
#define QUAKE3_MAP_NAME "maps/q3dm8.bsp"
#endif
#endif
#ifdef IRRLICHT_QUAKE3_ARENA
#define QUAKE3_STORAGE_FORMAT addFileArchive
#define QUAKE3_STORAGE_1 getExampleMediaPath() + "map-20kdm2.pk3"
#define QUAKE3_MAP_NAME "maps/20kdm2.bsp"
#endif
using namespace irr;
using namespace scene;

Again, to be able to use the Irrlicht.DLL file, we need to link with the Irrlicht.lib. We could set this option in the project settings, but to make it easy, we use a pragma comment lib:

#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

A class to produce a series of screenshots

class CScreenShotFactory : public IEventReceiver
{
public:
CScreenShotFactory( IrrlichtDevice *device, const c8 * templateName, ISceneNode* node )
: Device(device), Number(0), FilenameTemplate(templateName), Node(node)
{
FilenameTemplate.replace ( '/', '_' );
FilenameTemplate.replace ( '\\', '_' );
}
bool OnEvent(const SEvent& event)
{
// check if user presses the key F9
if ((event.EventType == EET_KEY_INPUT_EVENT) &&
event.KeyInput.PressedDown)
{
if (event.KeyInput.Key == KEY_F9)
{
video::IImage* image = Device->getVideoDriver()->createScreenShot();
if (image)
{
c8 buf[256];
snprintf_irr(buf, 256, "%s_shot%04d.jpg",
FilenameTemplate.c_str(),
++Number);
Device->getVideoDriver()->writeImageToFile(image, buf, 85 );
image->drop();
}
}
else
if (event.KeyInput.Key == KEY_F8)
{
if (Node->isDebugDataVisible())
Node->setDebugDataVisible(scene::EDS_OFF);
else
Node->setDebugDataVisible(scene::EDS_BBOX_ALL);
}
}
return false;
}
private:
IrrlichtDevice *Device;
u32 Number;
core::stringc FilenameTemplate;
ISceneNode* Node;
};

Ok, lets start.

int IRRCALLCONV main(int argc, char* argv[])
{

Like in the HelloWorld example, we create an IrrlichtDevice with createDevice(). The difference now is that we ask the user to select which hardware accelerated driver to use. The Software device would be too slow to draw a huge Quake 3 map, but just for the fun of it, we make this decision possible too.

// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// create device and exit if creation failed
const core::dimension2du videoDim(800,600);
IrrlichtDevice *device = createDevice(driverType, videoDim, 32, false );
if (device == 0)
return 1; // could not create selected driver.
const char* mapname=0;
if (argc>2)
mapname = argv[2];
else
mapname = QUAKE3_MAP_NAME;

Get a pointer to the video driver and the SceneManager so that we do not always have to write device->getVideoDriver() and device->getSceneManager().

video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* gui = device->getGUIEnvironment();
const io::path mediaPath = getExampleMediaPath();
device->getFileSystem()->addFileArchive(mediaPath);

To display the Quake 3 map, we first need to load it. Quake 3 maps are packed into .pk3 files, which are nothing other than .zip files. So we add the .pk3 file to our FileSystem. After it was added, we are able to read from the files in that archive as they would directly be stored on disk.

if (argc>2)
device->getFileSystem()->QUAKE3_STORAGE_FORMAT(argv[1]);
else
device->getFileSystem()->QUAKE3_STORAGE_FORMAT(QUAKE3_STORAGE_1);
#ifdef QUAKE3_STORAGE_2
device->getFileSystem()->QUAKE3_STORAGE_FORMAT(QUAKE3_STORAGE_2);
#endif
// Quake3 Shader controls Z-Writing
smgr->getParameters()->setAttribute(scene::ALLOW_ZWRITE_ON_TRANSPARENT, true);

Now we can load the mesh by calling getMesh(). We get a pointer returned to a IAnimatedMesh. As you know, Quake 3 maps are not really animated, they are only a huge chunk of static geometry with some materials attached. Hence the IAnimated mesh consists of only one frame, so we get the "first frame" of the "animation", which is our quake level and create an Octree scene node with it, using addOctreeSceneNode(). The Octree optimizes the scene a little bit, trying to draw only geometry which is currently visible. An alternative to the Octree would be a AnimatedMeshSceneNode, which would draw always the complete geometry of the mesh, without optimization. Try it out: Write addAnimatedMeshSceneNode instead of addOctreeSceneNode and compare the primitives drawn by the video driver. (There is a getPrimitiveCountDrawed() method in the IVideoDriver class). Note that this optimization with the Octree is only useful when drawing huge meshes consisting of lots of geometry.

scene::IQ3LevelMesh* const mesh =
(scene::IQ3LevelMesh*) smgr->getMesh(mapname);

add the geometry mesh to the Scene ( polygon & patches ) The Geometry mesh is optimised for faster drawing

scene::ISceneNode* node = 0;
if (mesh)
{
scene::IMesh * const geometry = mesh->getMesh(quake3::E_Q3_MESH_GEOMETRY);
node = smgr->addOctreeSceneNode(geometry, 0, -1, 4096);
}
// create an event receiver for making screenshots
CScreenShotFactory screenshotFactory(device, mapname, node);
device->setEventReceiver(&screenshotFactory);

now construct SceneNodes for each Shader The Objects are stored in the quake mesh scene::E_Q3_MESH_ITEMS and the Shader ID is stored in the MaterialParameters mostly dark looking skulls and moving lava.. or green flashing tubes?

if ( mesh )
{
// the additional mesh can be quite huge and is unoptimized
const scene::IMesh * const additional_mesh = mesh->getMesh(quake3::E_Q3_MESH_ITEMS);
#ifdef SHOW_SHADER_NAME
gui::IGUIFont *font = device->getGUIEnvironment()->getFont(mediaPath + "fontlucida.png");
u32 count = 0;
#endif
for ( u32 i = 0; i!= additional_mesh->getMeshBufferCount(); ++i )
{
const IMeshBuffer* meshBuffer = additional_mesh->getMeshBuffer(i);
const video::SMaterial& material = meshBuffer->getMaterial();
// The ShaderIndex is stored in the material parameter
const s32 shaderIndex = (s32) material.MaterialTypeParam2;
// the meshbuffer can be rendered without additional support, or it has no shader
const quake3::IShader *shader = mesh->getShader(shaderIndex);
if (0 == shader)
{
continue;
}
// we can dump the shader to the console in its
// original but already parsed layout in a pretty
// printers way.. commented out, because the console
// would be full...
// quake3::dumpShader ( Shader );
node = smgr->addQuake3SceneNode(meshBuffer, shader);
#ifdef SHOW_SHADER_NAME
count += 1;
core::stringw name( node->getName() );
node = smgr->addBillboardTextSceneNode(
font, name.c_str(), node,
core::dimension2d<f32>(80.0f, 8.0f),
core::vector3df(0, 10, 0));
#endif
}
}

Now we only need a Camera to look at the Quake 3 map. And we want to create a user controlled camera. There are some different cameras available in the Irrlicht engine. For example the Maya Camera which can be controlled comparable to the camera in Maya: Rotate with left mouse button pressed, Zoom with both buttons pressed, translate with right mouse button pressed. This could be created with addCameraSceneNodeMaya(). But for this example, we want to create a camera which behaves like the ones in first person shooter games (FPS).

scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();

so we need a good starting Position in the level. we can ask the Quake3 Loader for all entities with class_name "info_player_deathmatch" we choose a random launch

if ( mesh )
{
quake3::tQ3EntityList &entityList = mesh->getEntityList();
quake3::IEntity search;
search.name = "info_player_deathmatch";
s32 index = entityList.binary_search(search);
if (index >= 0)
{
s32 notEndList;
do
{
const quake3::SVarGroup *group = entityList[index].getGroup(1);
u32 parsepos = 0;
const core::vector3df pos =
quake3::getAsVector3df(group->get("origin"), parsepos);
parsepos = 0;
const f32 angle = quake3::getAsFloat(group->get("angle"), parsepos);
core::vector3df target(0.f, 0.f, 1.f);
target.rotateXZBy(angle);
camera->setPosition(pos);
camera->setTarget(pos + target);
++index;

notEndList = ( index < (s32) entityList.size () && entityList[index].name == search.name && (device->getTimer()->getRealTime() >> 3 ) & 1 );

notEndList = index == 2;
} while ( notEndList );
}
}

The mouse cursor needs not to be visible, so we make it invisible.

device->getCursorControl()->setVisible(false);
// load the engine logo
gui->addImage(driver->getTexture("irrlichtlogo3.png"),
core::position2d<s32>(10, 10));
// show the driver logo
const core::position2di pos(videoDim.Width - 128, videoDim.Height - 64);
switch ( driverType )
{
case video::EDT_BURNINGSVIDEO:
gui->addImage(driver->getTexture("burninglogo.png"), pos);
break;
case video::EDT_OPENGL:
gui->addImage(driver->getTexture("opengllogo.png"), pos);
break;
case video::EDT_DIRECT3D9:
gui->addImage(driver->getTexture("directxlogo.png"), pos);
break;
default:
break;
}

We have done everything, so lets draw it. We also write the current frames per second and the drawn primitives to the caption of the window. The 'if (device->isWindowActive())' line is optional, but prevents the engine render to set the position of the mouse cursor after task switching when other program are active.

int lastFPS = -1;
while(device->run())
if (device->isWindowActive())
{
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(255,20,20,40));
smgr->drawAll();
gui->drawAll();
driver->endScene();
int fps = driver->getFPS();
if (1 || lastFPS != fps)
{
core::stringw str = L"Q3 [";
str += driver->getName();
str += "] FPS:";
str += fps;
#ifdef _IRR_SCENEMANAGER_DEBUG
io::IAttributes * const attr = smgr->getParameters();
str += " Cull:";
str += attr->getAttributeAsInt("calls");
str += "/";
str += attr->getAttributeAsInt("culled");
str += " Draw: ";
str += attr->getAttributeAsInt("drawn_solid");
str += "/";
str += attr->getAttributeAsInt("drawn_transparent");
str += "/";
str += attr->getAttributeAsInt("drawn_transparent_effect");
#endif
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}

In the end, delete the Irrlicht device.

device->drop();
return 0;
}
+ + +

 

+ + diff --git a/examples/17.HelloWorld_Mobile/main.cpp b/examples/17.HelloWorld_Mobile/main.cpp index 9db5806f..69374c26 100644 --- a/examples/17.HelloWorld_Mobile/main.cpp +++ b/examples/17.HelloWorld_Mobile/main.cpp @@ -1,8 +1,9 @@ -/** Deprecated. This was Example 017 Helloworld mobile for WinCE 6. - But WinCE6 support has been removed for Irrlicht 1.9. +/** Example 017 Helloworld mobile for WinCE 6. DEPRECATED + This was Example 017 Helloworld mobile for WinCE 6. + But WinCE6 support has been removed for Irrlicht 1.9. If you still need that please use Irrlicht 1.8 or svn revision 5045 which was the last one to include it. - Sources still kept for now as it compiles on other platform too. And we might use this example again + Sources still kept for now as it compiles on other platform too. And we might use this example again once we support Windows RT. */ @@ -101,7 +102,7 @@ public: virtual SMaterial& getMaterial(u32 i) { return Material; - } + } }; /*! @@ -186,7 +187,7 @@ int example_customscenenode() // create engine and camera EventReceiver_basic receiver(device); device->setEventReceiver(&receiver); - + IVideoDriver* driver = device->getVideoDriver(); ISceneManager* smgr = device->getSceneManager(); IGUIEnvironment* guienv = device->getGUIEnvironment(); @@ -194,10 +195,10 @@ int example_customscenenode() smgr->addCameraSceneNode(0, vector3df(0,-40,0), vector3df(0,0,0)); - CSampleSceneNode *myNode = + CSampleSceneNode *myNode = new CSampleSceneNode(smgr->getRootSceneNode(), smgr, 666); - ISceneNodeAnimator* anim = + ISceneNodeAnimator* anim = smgr->createRotationAnimator(vector3df(0.8f, 0, 0.8f)); if(anim) @@ -293,7 +294,7 @@ int example_terrain() IrrlichtDevice *device = startup(); if (device == 0) return 1; // could not create selected driver. - + /* First, we add standard stuff to the scene: A nice irrlicht engine logo, a small help text, a user controlled camera, and we disable @@ -363,7 +364,7 @@ int example_terrain() driver->getTexture("../../media/terrain-texture.jpg")); terrain->setMaterialTexture(1, driver->getTexture("../../media/detailmap3.jpg")); - + terrain->setMaterialType(video::EMT_DETAIL_MAP); terrain->scaleTexture(1.0f, 20.0f); @@ -378,7 +379,7 @@ int example_terrain() through the terrain. */ - // create triangle selector for the terrain + // create triangle selector for the terrain scene::ITriangleSelector* selector = smgr->createTerrainTriangleSelector(terrain, 0); terrain->setTriangleSelector(selector); diff --git a/examples/17.HelloWorld_Mobile/tutorial.html b/examples/17.HelloWorld_Mobile/tutorial.html new file mode 100644 index 00000000..e0e6d3c3 --- /dev/null +++ b/examples/17.HelloWorld_Mobile/tutorial.html @@ -0,0 +1,207 @@ + + + + + + + +Tutorial 17: Helloworld mobile for WinCE 6. DEPRECATED + + + + +
+ + + +
+
+
+
Tutorial 17: Helloworld mobile for WinCE 6. DEPRECATED
+
+
+
+017shot.jpg +
+ This was Example 017 Helloworld mobile for WinCE 6. But WinCE6 support has been removed for Irrlicht 1.9. If you still need that please use Irrlicht 1.8 or svn revision 5045 which was the last one to include it.

+

Sources still kept for now as it compiles on other platform too. And we might use this example again once we support Windows RT.

#include <irrlicht.h>
#if defined ( _IRR_WINDOWS_ )
#include <windows.h>
#endif
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;
#pragma comment(lib, "Irrlicht.lib")
class EventReceiver_basic : public IEventReceiver
{
private:
IrrlichtDevice *Device;
public:
EventReceiver_basic ( IrrlichtDevice *device ): Device ( device ) {}
virtual bool OnEvent(const SEvent& event)
{
if (event.EventType == EET_GUI_EVENT)
{
s32 id = event.GUIEvent.Caller->getID();
switch(event.GUIEvent.EventType)
{
case EGET_BUTTON_CLICKED:
if (id == 2)
{
Device->closeDevice();
return true;
} break;
}
}
return false;
}
};
class CSampleSceneNode : public ISceneNode
{
aabbox3d<f32> Box;
S3DVertex Vertices[4];
SMaterial Material;
public:
CSampleSceneNode(ISceneNode* parent, ISceneManager* mgr, s32 id)
: ISceneNode(parent, mgr, id)
{
Material.Wireframe = false;
Material.Lighting = false;
Vertices[0] = S3DVertex(0,0,10, 1,1,0, SColor(255,0,255,255), 0, 1);
Vertices[1] = S3DVertex(10,0,-10, 1,0,0, SColor(255,255,0,255), 1, 1);
Vertices[2] = S3DVertex(0,20,0, 0,1,1, SColor(255,255,255,0), 1, 0);
Vertices[3] = S3DVertex(-10,0,-10, 0,0,1, SColor(255,0,255,0), 0, 0);
Box.reset(Vertices[0].Pos);
for (s32 i=1; i<4; ++i)
Box.addInternalPoint(Vertices[i].Pos);
}
virtual void OnRegisterSceneNode()
{
if (IsVisible)
SceneManager->registerNodeForRendering(this);
ISceneNode::OnRegisterSceneNode();
}
virtual void render()
{
u16 indices[] = { 0,2,3, 2,1,3, 1,0,3, 2,0,1 };
IVideoDriver* driver = SceneManager->getVideoDriver();
driver->setMaterial(Material);
driver->setTransform(ETS_WORLD, AbsoluteTransformation);
driver->drawIndexedTriangleList(&Vertices[0], 4, &indices[0], 4);
}
virtual const aabbox3d<f32>& getBoundingBox() const
{
return Box;
}
virtual u32 getMaterialCount()
{
return 1;
}
virtual SMaterial& getMaterial(u32 i)
{
return Material;
}
};

! Startup a Windows Mobile Device

IrrlichtDevice *startup()
{
// both software and burnings video can be used
E_DRIVER_TYPE driverType = EDT_SOFTWARE; // EDT_BURNINGSVIDEO;
// create device
IrrlichtDevice *device = 0;
// Use window mode on PC
device = createDevice(driverType, dimension2d<u32>(240, 320), 16, false );
if ( 0 == device )
return 0;
IVideoDriver* driver = device->getVideoDriver();
ISceneManager* smgr = device->getSceneManager();
IGUIEnvironment* guienv = device->getGUIEnvironment();
// set the filesystem relative to the executable
#if defined (_IRR_WINDOWS_)
{
wchar_t buf[255];
GetModuleFileNameW ( 0, buf, 255 );
io::path base = buf;
base = base.subString ( 0, base.findLast ( '\\' ) + 1 );
device->getFileSystem()->addFileArchive ( base );
}
#endif
IGUIStaticText *text = guienv->addStaticText(L"FPS: 25",
rect<s32>(140,15,200,30), false, false, 0, 100 );
guienv->addButton(core::rect<int>(200,10,238,30), 0, 2, L"Quit");
// add irrlicht logo
guienv->addImage(driver->getTexture("../../media/irrlichtlogo3.png"),
core::position2d<s32>(0,-2));
return device;
}

!

int run ( IrrlichtDevice *device )
{
while(device->run())
if (device->isWindowActive())
{
device->getVideoDriver()->beginScene(true, true, SColor(0,100,100,100));
device->getSceneManager()->drawAll();
device->getGUIEnvironment()->drawAll();
device->getVideoDriver()->endScene ();
IGUIElement *stat = device->getGUIEnvironment()->
getRootGUIElement()->getElementFromId ( 100 );
if ( stat )
{
stringw str = L"FPS: ";
str += (s32)device->getVideoDriver()->getFPS();
stat->setText ( str.c_str() );
}
}
device->drop();
return 0;
}

!

int example_customscenenode()
{
// create device
IrrlichtDevice *device = startup();
if (device == 0)
return 1; // could not create selected driver.
// create engine and camera
EventReceiver_basic receiver(device);
device->setEventReceiver(&receiver);
IVideoDriver* driver = device->getVideoDriver();
ISceneManager* smgr = device->getSceneManager();
IGUIEnvironment* guienv = device->getGUIEnvironment();
smgr->addCameraSceneNode(0, vector3df(0,-40,0), vector3df(0,0,0));
CSampleSceneNode *myNode =
new CSampleSceneNode(smgr->getRootSceneNode(), smgr, 666);
ISceneNodeAnimator* anim =
smgr->createRotationAnimator(vector3df(0.8f, 0, 0.8f));
if(anim)
{
myNode->addAnimator(anim);
anim->drop();
anim = 0; // As I shouldn't refer to it again, ensure that I can't
}
myNode->drop();
myNode = 0; // As I shouldn't refer to it again, ensure that I can't
return run ( device );
}
class EventReceiver_terrain : public IEventReceiver
{
public:
EventReceiver_terrain(IrrlichtDevice *device, scene::ISceneNode* terrain, scene::ISceneNode* skybox, scene::ISceneNode* skydome) :
Device ( device ), Terrain(terrain), Skybox(skybox), Skydome(skydome), showBox(true)
{
Skybox->setVisible(true);
Skydome->setVisible(false);
}
bool OnEvent(const SEvent& event)
{
if (event.EventType == EET_GUI_EVENT)
{
s32 id = event.GUIEvent.Caller->getID();
switch(event.GUIEvent.EventType)
{
case EGET_BUTTON_CLICKED:
if (id == 2)
{
Device->closeDevice();
return true;
} break;
}
}
// check if user presses the key 'W' or 'D'
if (event.EventType == irr::EET_KEY_INPUT_EVENT && !event.KeyInput.PressedDown)
{
switch (event.KeyInput.Key)
{
case irr::KEY_KEY_W: // switch wire frame mode
Terrain->setMaterialFlag(video::EMF_WIREFRAME,
!Terrain->getMaterial(0).Wireframe);
Terrain->setMaterialFlag(video::EMF_POINTCLOUD, false);
return true;
case irr::KEY_KEY_P: // switch wire frame mode
Terrain->setMaterialFlag(video::EMF_POINTCLOUD,
!Terrain->getMaterial(0).PointCloud);
Terrain->setMaterialFlag(video::EMF_WIREFRAME, false);
return true;
case irr::KEY_KEY_D: // toggle detail map
Terrain->setMaterialType(
Terrain->getMaterial(0).MaterialType == video::EMT_SOLID ?
video::EMT_DETAIL_MAP : video::EMT_SOLID);
return true;
case irr::KEY_KEY_S: // toggle skies
showBox=!showBox;
Skybox->setVisible(showBox);
Skydome->setVisible(!showBox);
return true;
default:
break;
}
}
return false;
}
private:
IrrlichtDevice *Device;
scene::ISceneNode* Terrain;
scene::ISceneNode* Skybox;
scene::ISceneNode* Skydome;
bool showBox;
};

The start of the main function starts like in most other example. We ask the user for the desired renderer and start it up. This time with the advanced parameter handling.

int example_terrain()
{
// create device
IrrlichtDevice *device = startup();
if (device == 0)
return 1; // could not create selected driver.

First, we add standard stuff to the scene: A nice irrlicht engine logo, a small help text, a user controlled camera, and we disable the mouse cursor.

video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* env = device->getGUIEnvironment();
//set other font
//env->getSkin()->setFont(env->getFont("../../media/fontlucida.png"));
// add some help text
env->addStaticText(
L"Press 'W' to change wireframe mode\nPress 'D' to toggle detail map\nPress 'S' to toggle skybox/skydome",
core::rect<s32>(5,250,235,320), true, true, 0, -1, true);
// add camera
scene::ICameraSceneNode* camera =
smgr->addCameraSceneNodeFPS(0,100.0f,1.2f);
camera->setPosition(core::vector3df(2700*2,255*2,2600*2));
camera->setTarget(core::vector3df(2397*2,343*2,2700*2));
camera->setFarValue(42000.0f);
// disable mouse cursor
device->getCursorControl()->setVisible(false);

Here comes the terrain renderer scene node: We add it just like any other scene node to the scene using ISceneManager::addTerrainSceneNode(). The only parameter we use is a file name to the heightmap we use. A heightmap is simply a gray scale texture. The terrain renderer loads it and creates the 3D terrain from it.

+

To make the terrain look more big, we change the scale factor of it to (40, 4.4, 40). Because we don't have any dynamic lights in the scene, we switch off the lighting, and we set the file terrain-texture.jpg as texture for the terrain and detailmap3.jpg as second texture, called detail map. At last, we set the scale values for the texture: The first texture will be repeated only one time over the whole terrain, and the second one (detail map) 20 times.

// add terrain scene node
scene::ITerrainSceneNode* terrain = smgr->addTerrainSceneNode(
"../../media/terrain-heightmap.bmp",
0, // parent node
-1, // node id
core::vector3df(0.f, 0.f, 0.f), // position
core::vector3df(0.f, 0.f, 0.f), // rotation
core::vector3df(40.f, 4.4f, 40.f), // scale
video::SColor ( 255, 255, 255, 255 ), // vertexColor
5, // maxLOD
scene::ETPS_17, // patchSize
4 // smoothFactor
);
if ( terrain )
{
terrain->setMaterialFlag(video::EMF_LIGHTING, false);
terrain->setMaterialTexture(0,
driver->getTexture("../../media/terrain-texture.jpg"));
terrain->setMaterialTexture(1,
driver->getTexture("../../media/detailmap3.jpg"));
terrain->setMaterialType(video::EMT_DETAIL_MAP);
terrain->scaleTexture(1.0f, 20.0f);
//terrain->setDebugDataVisible ( true );

To be able to do collision with the terrain, we create a triangle selector. If you want to know what triangle selectors do, just take a look into the collision tutorial. The terrain triangle selector works together with the terrain. To demonstrate this, we create a collision response animator and attach it to the camera, so that the camera will not be able to fly through the terrain.

// create triangle selector for the terrain
scene::ITriangleSelector* selector
= smgr->createTerrainTriangleSelector(terrain, 0);
terrain->setTriangleSelector(selector);
// create collision response animator and attach it to the camera
scene::ISceneNodeAnimator* anim = smgr->createCollisionResponseAnimator(
selector, camera, core::vector3df(60,100,60),
core::vector3df(0,0,0),
core::vector3df(0,50,0));
selector->drop();
camera->addAnimator(anim);
anim->drop();

If you need access to the terrain data you can also do this directly via the following code fragment.

scene::CDynamicMeshBuffer* buffer = new scene::CDynamicMeshBuffer(video::EVT_2TCOORDS, video::EIT_16BIT);
terrain->getMeshBufferForLOD(*buffer, 0);
video::S3DVertex2TCoords* data = (video::S3DVertex2TCoords*)buffer->getVertexBuffer().getData();
// Work on data or get the IndexBuffer with a similar call.
buffer->drop(); // When done drop the buffer again.
}

To make the user be able to switch between normal and wireframe mode, we create an instance of the event receiver from above and let Irrlicht know about it. In addition, we add the skybox which we already used in lots of Irrlicht examples and a skydome, which is shown mutually exclusive with the skybox by pressing 'S'.

// create skybox and skydome
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
scene::ISceneNode* skybox=smgr->addSkyBoxSceneNode(
driver->getTexture("../../media/irrlicht2_up.jpg"),
driver->getTexture("../../media/irrlicht2_dn.jpg"),
driver->getTexture("../../media/irrlicht2_lf.jpg"),
driver->getTexture("../../media/irrlicht2_rt.jpg"),
driver->getTexture("../../media/irrlicht2_ft.jpg"),
driver->getTexture("../../media/irrlicht2_bk.jpg"));
scene::ISceneNode* skydome=smgr->addSkyDomeSceneNode(driver->getTexture("../../media/skydome.jpg"),16,8,0.95f,2.0f);
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, true);
// create event receiver
EventReceiver_terrain receiver( device, terrain, skybox, skydome);
device->setEventReceiver(&receiver);
return run ( device );
}
int example_helloworld()
{
// create device
IrrlichtDevice *device = startup();
if (device == 0)
return 1; // could not create selected driver.
IVideoDriver* driver = device->getVideoDriver();
ISceneManager* smgr = device->getSceneManager();
IGUIEnvironment* guienv = device->getGUIEnvironment();
IAnimatedMesh* mesh = smgr->getMesh("../../media/sydney.md2");
if (!mesh)
{
device->drop();
return 1;
}
IAnimatedMeshSceneNode* node = smgr->addAnimatedMeshSceneNode( mesh );

To let the mesh look a little bit nicer, we change its material. We disable lighting because we do not have a dynamic light in here, and the mesh would be totally black otherwise. Then we set the frame loop, such that the predefined STAND animation is used. And last, we apply a texture to the mesh. Without it the mesh would be drawn using only a color.

if (node)
{
node->setMaterialFlag(EMF_LIGHTING, false);
node->setMD2Animation(scene::EMAT_STAND);
node->setMaterialTexture( 0, driver->getTexture("../../media/sydney.bmp") );
}

To look at the mesh, we place a camera into 3d space at the position (0, 30, -40). The camera looks from there to (0,5,0), which is approximately the place where our md2 model is.

smgr->addCameraSceneNode(0, vector3df(0,30,-40), vector3df(0,5,0));
EventReceiver_basic receiver(device);
device->setEventReceiver(&receiver);
return run ( device );
}
#if defined (_IRR_WINDOWS_)
#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
#endif
int main()
{
example_helloworld ();
example_customscenenode();
//example_terrain();
}
+ + +

 

+ + diff --git a/examples/18.SplitScreen/main.cpp b/examples/18.SplitScreen/main.cpp index 600b7bf8..85aa403d 100644 --- a/examples/18.SplitScreen/main.cpp +++ b/examples/18.SplitScreen/main.cpp @@ -170,6 +170,9 @@ Sounds a little complicated, but you'll see it isn't: while(device->run()) { + // Don't reset mouse cursor when window is not active + camera[3]->setInputReceiverEnabled(device->isWindowActive()); + //Set the viewpoint to the whole screen and begin scene driver->setViewPort(rect(0,0,ResX,ResY)); driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, SColor(255,100,100,100)); diff --git a/examples/18.SplitScreen/tutorial.html b/examples/18.SplitScreen/tutorial.html new file mode 100644 index 00000000..0df8a01b --- /dev/null +++ b/examples/18.SplitScreen/tutorial.html @@ -0,0 +1,236 @@ + + + + + + + +Tutorial 18: Splitscreen + + + + +
+ + + +
+
+
+
Tutorial 18: Splitscreen
+
+
+
+018shot.jpg +
+

A tutorial by Max Winkel.

+

In this tutorial we'll learn how to use splitscreen (e.g. for racing-games) with Irrlicht. We'll create a viewport divided into 4 parts, with 3 fixed cameras and one user-controlled.

+

Ok, let's start with the headers (I think there's nothing to say about it)

#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
//Namespaces for the engine
using namespace irr;
using namespace core;
using namespace video;
using namespace scene;

Now we'll define the resolution in a constant for use in initializing the device and setting up the viewport. In addition we set up a global variable saying splitscreen is active or not.

//Resolution
const int ResX=800;
const int ResY=600;
const bool fullScreen=false;
//Use SplitScreen?
bool SplitScreen=true;

Now we need four pointers to our cameras which are created later:

//cameras
ICameraSceneNode *camera[4]={0,0,0,0};

In our event-receiver we switch the SplitScreen-variable, whenever the user press the S-key. All other events are sent to the FPS camera.

class MyEventReceiver : public IEventReceiver
{
public:
virtual bool OnEvent(const SEvent& event)
{
//Key S enables/disables SplitScreen
if (event.EventType == irr::EET_KEY_INPUT_EVENT &&
event.KeyInput.Key == KEY_KEY_S && event.KeyInput.PressedDown)
{
SplitScreen = !SplitScreen;
return true;
}
//Send all other events to camera4
if (camera[3])
return camera[3]->OnEvent(event);
return false;
}
};

Ok, now the main-function: First, we initialize the device, get the SourceManager and VideoDriver, load an animated mesh from .md2 and a map from .pk3. Because that's old stuff, I won't explain every step. Just take care of the maps position.

int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
//Instance of the EventReceiver
MyEventReceiver receiver;
//Initialise the engine
IrrlichtDevice *device = createDevice(driverType,
dimension2du(ResX,ResY), 32, fullScreen,
false, false, &receiver);
if (!device)
return 1;
ISceneManager *smgr = device->getSceneManager();
IVideoDriver *driver = device->getVideoDriver();
const io::path mediaPath = getExampleMediaPath();
//Load model
IAnimatedMesh *model = smgr->getMesh(mediaPath + "sydney.md2");
if (!model)
return 1;
IAnimatedMeshSceneNode *model_node = smgr->addAnimatedMeshSceneNode(model);
//Load texture
if (model_node)
{
ITexture *texture = driver->getTexture(mediaPath + "sydney.bmp");
model_node->setMaterialTexture(0,texture);
model_node->setMD2Animation(scene::EMAT_RUN);
//Disable lighting (we've got no light)
model_node->setMaterialFlag(EMF_LIGHTING,false);
}
//Load map
device->getFileSystem()->addFileArchive(mediaPath + "map-20kdm2.pk3");
IAnimatedMesh *map = smgr->getMesh("20kdm2.bsp");
if (map)
{
ISceneNode *map_node = smgr->addOctreeSceneNode(map->getMesh(0));
//Set position
map_node->setPosition(vector3df(-850,-220,-850));
}

Now we create our four cameras. One is looking at the model from the front, one from the top and one from the side. In addition there's a FPS-camera which can be controlled by the user.

// Create 3 fixed and one user-controlled cameras
//Front
camera[0] = smgr->addCameraSceneNode(0, vector3df(50,0,0), vector3df(0,0,0));
//Top
camera[1] = smgr->addCameraSceneNode(0, vector3df(0,50,0), vector3df(0,0,0));
//Left
camera[2] = smgr->addCameraSceneNode(0, vector3df(0,0,50), vector3df(0,0,0));
//User-controlled
camera[3] = smgr->addCameraSceneNodeFPS();
// don't start at sydney's position
if (camera[3])
camera[3]->setPosition(core::vector3df(-50,0,-50));

Create a variable for counting the fps and hide the mouse:

//Hide mouse
device->getCursorControl()->setVisible(false);
//We want to count the fps
int lastFPS = -1;

There wasn't much new stuff - till now! Only by defining four cameras, the game won't be splitscreen. To do this you need several steps:

    +
  • Set the viewport to the whole screen
  • +
  • Begin a new scene (Clear screen)
  • +
  • The following 3 steps are repeated for every viewport in the splitscreen
      +
    • Set the viewport to the area you wish
    • +
    • Activate the camera which should be "linked" with the viewport
    • +
    • Render all objects
    • +
    +
  • +
  • If you have a GUI:
      +
    • Set the viewport the whole screen
    • +
    • Display the GUI
    • +
    +
  • +
  • End scene
  • +
+

Sounds a little complicated, but you'll see it isn't:

while(device->run())
{
//Set the viewpoint to the whole screen and begin scene
driver->setViewPort(rect<s32>(0,0,ResX,ResY));
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, SColor(255,100,100,100));
//If SplitScreen is used
if (SplitScreen)
{
//Activate camera1
smgr->setActiveCamera(camera[0]);
//Set viewpoint to the first quarter (left top)
driver->setViewPort(rect<s32>(0,0,ResX/2,ResY/2));
//Draw scene
smgr->drawAll();
//Activate camera2
smgr->setActiveCamera(camera[1]);
//Set viewpoint to the second quarter (right top)
driver->setViewPort(rect<s32>(ResX/2,0,ResX,ResY/2));
//Draw scene
smgr->drawAll();
//Activate camera3
smgr->setActiveCamera(camera[2]);
//Set viewpoint to the third quarter (left bottom)
driver->setViewPort(rect<s32>(0,ResY/2,ResX/2,ResY));
//Draw scene
smgr->drawAll();
//Set viewport the last quarter (right bottom)
driver->setViewPort(rect<s32>(ResX/2,ResY/2,ResX,ResY));
}
//Activate camera4
smgr->setActiveCamera(camera[3]);
//Draw scene
smgr->drawAll();
driver->endScene();

As you can probably see, the image is rendered for every viewport separately. That means, that you'll loose much performance. Ok, if you're asking "How do I have to set the viewport + to get this or that screen?", don't panic. It's really easy: In the rect-function you define 4 coordinates:

    +
  • X-coordinate of the corner left top
  • +
  • Y-coordinate of the corner left top
  • +
  • X-coordinate of the corner right bottom
  • +
  • Y-coordinate of the corner right bottom
  • +
+

That means, if you want to split the screen into 2 viewports you would give the following coordinates:

    +
  • 1st viewport: 0,0,ResX/2,ResY
  • +
  • 2nd viewport: ResX/2,0,ResX,ResY
  • +
+

If you didn't fully understand, just play around with the example to check out what happens.

+

Now we just view the current fps and shut down the engine, when the user wants to:

//Get and show fps
if (driver->getFPS() != lastFPS)
{
lastFPS = driver->getFPS();
core::stringw tmp = L"Irrlicht SplitScreen-Example (FPS: ";
tmp += lastFPS;
tmp += ")";
device->setWindowCaption(tmp.c_str());
}
}
//Delete device
device->drop();
return 0;
}

That's it! Just compile and play around with the program. Note: With the S-Key you can switch between using splitscreen and not.

+
+ + +

 

+ + diff --git a/examples/19.MouseAndJoystick/tutorial.html b/examples/19.MouseAndJoystick/tutorial.html new file mode 100644 index 00000000..f96a47a1 --- /dev/null +++ b/examples/19.MouseAndJoystick/tutorial.html @@ -0,0 +1,205 @@ + + + + + + + +Tutorial 19: Mouse and Joystick + + + + +
+ + + +
+
+
+
Tutorial 19: Mouse and Joystick
+
+
+
+019shot.jpg +
+

This tutorial builds on example 04.Movement which showed how to handle keyboard events in Irrlicht. Here we'll handle mouse events and joystick events, if you have a joystick connected and a device that supports joysticks. These are currently Windows, Linux and SDL devices.

#ifdef _MSC_VER
// We'll define this to stop MSVC complaining about sprintf().
#define _CRT_SECURE_NO_WARNINGS
#pragma comment(lib, "Irrlicht.lib")
#endif
#include <irrlicht.h>
#include "driverChoice.h"
using namespace irr;

Just as we did in example 04.Movement, we'll store the latest state of the mouse and the first joystick, updating them as we receive events.

class MyEventReceiver : public IEventReceiver
{
public:
// We'll create a struct to record info on the mouse state
struct SMouseState
{
core::position2di Position;
bool LeftButtonDown;
SMouseState() : LeftButtonDown(false) { }
} MouseState;
// This is the one method that we have to implement
virtual bool OnEvent(const SEvent& event)
{
// Remember the mouse state
if (event.EventType == irr::EET_MOUSE_INPUT_EVENT)
{
switch(event.MouseInput.Event)
{
case EMIE_LMOUSE_PRESSED_DOWN:
MouseState.LeftButtonDown = true;
break;
case EMIE_LMOUSE_LEFT_UP:
MouseState.LeftButtonDown = false;
break;
case EMIE_MOUSE_MOVED:
MouseState.Position.X = event.MouseInput.X;
MouseState.Position.Y = event.MouseInput.Y;
break;
default:
// We won't use the wheel
break;
}
}
// The state of each connected joystick is sent to us
// once every run() of the Irrlicht device. Store the
// state of the first joystick, ignoring other joysticks.
// This is currently only supported on Windows and Linux.
if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT
&& event.JoystickEvent.Joystick == 0)
{
JoystickState = event.JoystickEvent;
}
return false;
}
const SEvent::SJoystickEvent & GetJoystickState(void) const
{
return JoystickState;
}
const SMouseState & GetMouseState(void) const
{
return MouseState;
}
MyEventReceiver()
{
}
private:
SEvent::SJoystickEvent JoystickState;
};

The event receiver for keeping the pressed keys is ready, the actual responses will be made inside the render loop, right before drawing the scene. So lets just create an irr::IrrlichtDevice and the scene node we want to move. We also create some other additional scene nodes, to show that there are also some different possibilities to move and animate scene nodes.

int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// create device
MyEventReceiver receiver;
IrrlichtDevice* device = createDevice(driverType,
core::dimension2d<u32>(640, 480), 16, false, false, false, &receiver);
if (device == 0)
return 1; // could not create selected driver.
core::array<SJoystickInfo> joystickInfo;
if(device->activateJoysticks(joystickInfo))
{
std::cout << "Joystick support is enabled and " << joystickInfo.size() << " joystick(s) are present." << std::endl;
for(u32 joystick = 0; joystick < joystickInfo.size(); ++joystick)
{
std::cout << "Joystick " << joystick << ":" << std::endl;
std::cout << "\tName: '" << joystickInfo[joystick].Name.c_str() << "'" << std::endl;
std::cout << "\tAxes: " << joystickInfo[joystick].Axes << std::endl;
std::cout << "\tButtons: " << joystickInfo[joystick].Buttons << std::endl;
std::cout << "\tHat is: ";
switch(joystickInfo[joystick].PovHat)
{
case SJoystickInfo::POV_HAT_PRESENT:
std::cout << "present" << std::endl;
break;
case SJoystickInfo::POV_HAT_ABSENT:
std::cout << "absent" << std::endl;
break;
case SJoystickInfo::POV_HAT_UNKNOWN:
default:
std::cout << "unknown" << std::endl;
break;
}
}
}
else
{
std::cout << "Joystick support is not enabled." << std::endl;
}
core::stringw tmp = L"Irrlicht Joystick Example (";
tmp += joystickInfo.size();
tmp += " joysticks)";
device->setWindowCaption(tmp.c_str());
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();

We'll create an arrow mesh and move it around either with the joystick axis/hat, or make it follow the mouse pointer.

scene::ISceneNode * node = smgr->addMeshSceneNode(
smgr->addArrowMesh( "Arrow",
video::SColor(255, 255, 0, 0),
video::SColor(255, 0, 255, 0),
16,16,
2.f, 1.3f,
0.1f, 0.6f
)
);
node->setMaterialFlag(video::EMF_LIGHTING, false);
scene::ICameraSceneNode * camera = smgr->addCameraSceneNode();
camera->setPosition(core::vector3df(0, 0, -10));
// As in example 04, we'll use framerate independent movement.
u32 then = device->getTimer()->getTime();
const f32 MOVEMENT_SPEED = 5.f;
while(device->run())
{
// Work out a frame delta time.
const u32 now = device->getTimer()->getTime();
const f32 frameDeltaTime = (f32)(now - then) / 1000.f; // Time in seconds
then = now;
bool movedWithJoystick = false;
core::vector3df nodePosition = node->getPosition();
if(joystickInfo.size() > 0)
{
f32 moveHorizontal = 0.f; // Range is -1.f for full left to +1.f for full right
f32 moveVertical = 0.f; // -1.f for full down to +1.f for full up.
const SEvent::SJoystickEvent & joystickData = receiver.GetJoystickState();
// We receive the full analog range of the axes, and so have to implement our
// own dead zone. This is an empirical value, since some joysticks have more
// jitter or creep around the center point than others. We'll use 5% of the
// range as the dead zone, but generally you would want to give the user the
// option to change this.
const f32 DEAD_ZONE = 0.05f;
moveHorizontal =
(f32)joystickData.Axis[SEvent::SJoystickEvent::AXIS_X] / 32767.f;
if(fabs(moveHorizontal) < DEAD_ZONE)
moveHorizontal = 0.f;
moveVertical =
(f32)joystickData.Axis[SEvent::SJoystickEvent::AXIS_Y] / -32767.f;
if(fabs(moveVertical) < DEAD_ZONE)
moveVertical = 0.f;
// POV hat info is only currently supported on Windows, but the value is
// guaranteed to be 65535 if it's not supported, so we can check its range.
const u16 povDegrees = joystickData.POV / 100;
if(povDegrees < 360)
{
if(povDegrees > 0 && povDegrees < 180)
moveHorizontal = 1.f;
else if(povDegrees > 180)
moveHorizontal = -1.f;
if(povDegrees > 90 && povDegrees < 270)
moveVertical = -1.f;
else if(povDegrees > 270 || povDegrees < 90)
moveVertical = +1.f;
}
if(!core::equals(moveHorizontal, 0.f) || !core::equals(moveVertical, 0.f))
{
nodePosition.X += MOVEMENT_SPEED * frameDeltaTime * moveHorizontal;
nodePosition.Y += MOVEMENT_SPEED * frameDeltaTime * moveVertical;
movedWithJoystick = true;
}
}
// If the arrow node isn't being moved with the joystick, then have it follow the mouse cursor.
if(!movedWithJoystick)
{
// Create a ray through the mouse cursor.
core::line3df ray = smgr->getSceneCollisionManager()->getRayFromScreenCoordinates(
receiver.GetMouseState().Position, camera);
// And intersect the ray with a plane around the node facing towards the camera.
core::plane3df plane(nodePosition, core::vector3df(0, 0, -1));
core::vector3df mousePosition;
if(plane.getIntersectionWithLine(ray.start, ray.getVector(), mousePosition))
{
// We now have a mouse position in 3d space; move towards it.
core::vector3df toMousePosition(mousePosition - nodePosition);
const f32 availableMovement = MOVEMENT_SPEED * frameDeltaTime;
if(toMousePosition.getLength() <= availableMovement)
nodePosition = mousePosition; // Jump to the final position
else
nodePosition += toMousePosition.normalize() * availableMovement; // Move towards it
}
}
node->setPosition(nodePosition);
// Turn lighting on and off depending on whether the left mouse button is down.
node->setMaterialFlag(video::EMF_LIGHTING, receiver.GetMouseState().LeftButtonDown);
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(255,113,113,133));
smgr->drawAll(); // draw the 3d scene
driver->endScene();
}

In the end, delete the Irrlicht device.

device->drop();
return 0;
}
+ + +

 

+ + diff --git a/examples/20.ManagedLights/tutorial.html b/examples/20.ManagedLights/tutorial.html new file mode 100644 index 00000000..4bf4b290 --- /dev/null +++ b/examples/20.ManagedLights/tutorial.html @@ -0,0 +1,210 @@ + + + + + + + +Tutorial 20: Managed Lights + + + + +
+ + + +
+
+
+
Tutorial 20: Managed Lights
+
+
+
+020shot.jpg +
+

Written by Colin MacDonald. This tutorial explains the use of the Light Manager of Irrlicht. It enables the use of more dynamic light sources than the actual hardware supports. Further applications of the Light Manager, such as per scene node callbacks, are left out for simplicity of the example.

#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
using namespace irr;
using namespace core;
#if defined(_MSC_VER)
#pragma comment(lib, "Irrlicht.lib")
#endif // MSC_VER

Normally, you are limited to 8 dynamic lights per scene: this is a hardware limit. If you want to use more dynamic lights in your scene, then you can register an optional light manager that allows you to to turn lights on and off at specific point during rendering. You are still limited to 8 lights, but the limit is per scene node.

+

This is completely optional: if you do not register a light manager, then a default distance-based scheme will be used to prioritise hardware lights based on their distance from the active camera.

+

NO_MANAGEMENT disables the light manager and shows Irrlicht's default light behaviour. The 8 lights nearest to the camera will be turned on, and other lights will be turned off. In this example, this produces a funky looking but incoherent light display.

+

LIGHTS_NEAREST_NODE shows an implementation that turns on a limited number of lights per mesh scene node. If finds the 3 lights that are nearest to the node being rendered, and turns them on, turning all other lights off. This works, but as it operates on every light for every node, it does not scale well with many lights. The flickering you can see in this demo is due to the lights swapping their relative positions from the cubes (a deliberate demonstration of the limitations of this technique).

+

LIGHTS_IN_ZONE shows a technique for turning on lights based on a 'zone'. Each empty scene node is considered to be the parent of a zone. When nodes are rendered, they turn off all lights, then find their parent 'zone' and turn on all lights that are inside that zone, i.e. are descendents of it in the scene graph. This produces true 'local' lighting for each cube in this example. You could use a similar technique to locally light all meshes in (e.g.) a room, without the lights spilling out to other rooms.

+

This light manager is also an event receiver; this is purely for simplicity in this example, it's neither necessary nor recommended for a real application.

class CMyLightManager : public scene::ILightManager, public IEventReceiver
{
typedef enum
{
NO_MANAGEMENT,
LIGHTS_NEAREST_NODE,
LIGHTS_IN_ZONE
}
LightManagementMode;
LightManagementMode Mode;
LightManagementMode RequestedMode;
// These data represent the state information that this light manager
// is interested in.
scene::ISceneManager * SceneManager;
core::array<scene::ISceneNode*> * SceneLightList;
scene::E_SCENE_NODE_RENDER_PASS CurrentRenderPass;
scene::ISceneNode * CurrentSceneNode;
public:
CMyLightManager(scene::ISceneManager* sceneManager)
: Mode(NO_MANAGEMENT), RequestedMode(NO_MANAGEMENT),
SceneManager(sceneManager), SceneLightList(0),
CurrentRenderPass(scene::ESNRP_NONE), CurrentSceneNode(0)
{ }
// The input receiver interface, which just switches light management strategy
bool OnEvent(const SEvent & event)
{
bool handled = false;
if (event.EventType == irr::EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown)
{
handled = true;
switch(event.KeyInput.Key)
{
case irr::KEY_KEY_1:
RequestedMode = NO_MANAGEMENT;
break;
case irr::KEY_KEY_2:
RequestedMode = LIGHTS_NEAREST_NODE;
break;
case irr::KEY_KEY_3:
RequestedMode = LIGHTS_IN_ZONE;
break;
default:
handled = false;
break;
}
if(NO_MANAGEMENT == RequestedMode)
SceneManager->setLightManager(0); // Show that it's safe to register the light manager
else
SceneManager->setLightManager(this);
}
return handled;
}
// This is called before the first scene node is rendered.
virtual void OnPreRender(core::array<scene::ISceneNode*> & lightList)
{
// Update the mode; changing it here ensures that it's consistent throughout a render
Mode = RequestedMode;
// Store the light list. I am free to alter this list until the end of OnPostRender().
SceneLightList = &lightList;
}
// Called after the last scene node is rendered.
virtual void OnPostRender()
{
// Since light management might be switched off in the event handler, we'll turn all
// lights on to ensure that they are in a consistent state. You wouldn't normally have
// to do this when using a light manager, since you'd continue to do light management
// yourself.
for (u32 i = 0; i < SceneLightList->size(); i++)
(*SceneLightList)[i]->setVisible(true);
}
virtual void OnRenderPassPreRender(scene::E_SCENE_NODE_RENDER_PASS renderPass)
{
// I don't have to do anything here except remember which render pass I am in.
CurrentRenderPass = renderPass;
}
virtual void OnRenderPassPostRender(scene::E_SCENE_NODE_RENDER_PASS renderPass)
{
// I only want solid nodes to be lit, so after the solid pass, turn all lights off.
if (scene::ESNRP_SOLID == renderPass)
{
for (u32 i = 0; i < SceneLightList->size(); ++i)
(*SceneLightList)[i]->setVisible(false);
}
}
// This is called before the specified scene node is rendered
virtual void OnNodePreRender(scene::ISceneNode* node)
{
CurrentSceneNode = node;
// This light manager only considers solid objects, but you are free to manipulate
// lights during any phase, depending on your requirements.
if (scene::ESNRP_SOLID != CurrentRenderPass)
return;
// And in fact for this example, I only want to consider lighting for cube scene
// nodes. You will probably want to deal with lighting for (at least) mesh /
// animated mesh scene nodes as well.
if (node->getType() != scene::ESNT_CUBE)
return;
if (LIGHTS_NEAREST_NODE == Mode)
{
// This is a naive implementation that prioritises every light in the scene
// by its proximity to the node being rendered. This produces some flickering
// when lights orbit closer to a cube than its 'zone' lights.
const vector3df nodePosition = node->getAbsolutePosition();
// Sort the light list by prioritising them based on their distance from the node
// that's about to be rendered.
array<LightDistanceElement> sortingArray;
sortingArray.reallocate(SceneLightList->size());
u32 i;
for(i = 0; i < SceneLightList->size(); ++i)
{
scene::ISceneNode* lightNode = (*SceneLightList)[i];
const f64 distance = lightNode->getAbsolutePosition().getDistanceFromSQ(nodePosition);
sortingArray.push_back(LightDistanceElement(lightNode, distance));
}
sortingArray.sort();
// The list is now sorted by proximity to the node.
// Turn on the three nearest lights, and turn the others off.
for(i = 0; i < sortingArray.size(); ++i)
sortingArray[i].node->setVisible(i < 3);
}
else if(LIGHTS_IN_ZONE == Mode)
{
// Empty scene nodes are used to represent 'zones'. For each solid mesh that
// is being rendered, turn off all lights, then find its 'zone' parent, and turn
// on all lights that are found under that node in the scene graph.
// This is a general purpose algorithm that doesn't use any special
// knowledge of how this particular scene graph is organised.
for (u32 i = 0; i < SceneLightList->size(); ++i)
{
if ((*SceneLightList)[i]->getType() != scene::ESNT_LIGHT)
continue;
scene::ILightSceneNode* lightNode = static_cast<scene::ILightSceneNode*>((*SceneLightList)[i]);
video::SLight & lightData = lightNode->getLightData();
if (video::ELT_DIRECTIONAL != lightData.Type)
lightNode->setVisible(false);
}
scene::ISceneNode * parentZone = findZone(node);
if (parentZone)
turnOnZoneLights(parentZone);
}
}
// Called after the specified scene node is rendered
virtual void OnNodePostRender(scene::ISceneNode* node)
{
// I don't need to do any light management after individual node rendering.
}
private:
// Find the empty scene node that is the parent of the specified node
scene::ISceneNode * findZone(scene::ISceneNode * node)
{
if (!node)
return 0;
if (node->getType() == scene::ESNT_EMPTY)
return node;
return findZone(node->getParent());
}
// Turn on all lights that are children (directly or indirectly) of the
// specified scene node.
void turnOnZoneLights(scene::ISceneNode * node)
{
core::list<scene::ISceneNode*> const & children = node->getChildren();
for (core::list<scene::ISceneNode*>::ConstIterator child = children.begin();
child != children.end(); ++child)
{
if ((*child)->getType() == scene::ESNT_LIGHT)
(*child)->setVisible(true);
else // Assume that lights don't have any children that are also lights
turnOnZoneLights(*child);
}
}
// A utility class to aid in sorting scene nodes into a distance order
class LightDistanceElement
{
public:
LightDistanceElement() {};
LightDistanceElement(scene::ISceneNode* n, f64 d)
: node(n), distance(d) { }
scene::ISceneNode* node;
f64 distance;
// Lower distance elements are sorted to the start of the array
bool operator < (const LightDistanceElement& other) const
{
return (distance < other.distance);
}
};
};
int main(int argumentCount, char * argumentValues[])
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
IrrlichtDevice *device = createDevice(driverType,
dimension2d<u32>(640, 480), 32);
if(!device)
return -1;
f32 const lightRadius = 60.f; // Enough to reach the far side of each 'zone'
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* guienv = device->getGUIEnvironment();
const io::path mediaPath = getExampleMediaPath();
gui::IGUISkin* skin = guienv->getSkin();
if (skin)
{
skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255, 255, 255, 255));
gui::IGUIFont* font = guienv->getFont(mediaPath + "fontlucida.png");
if(font)
skin->setFont(font);
}
guienv->addStaticText(L"1 - No light management", core::rect<s32>(10,10,200,30));
guienv->addStaticText(L"2 - Closest 3 lights", core::rect<s32>(10,30,200,50));
guienv->addStaticText(L"3 - Lights in zone", core::rect<s32>(10,50,200,70));

Add several "zones". You could use this technique to light individual rooms, for example.

for(f32 zoneX = -100.f; zoneX <= 100.f; zoneX += 50.f)
for(f32 zoneY = -60.f; zoneY <= 60.f; zoneY += 60.f)
{
// Start with an empty scene node, which we will use to represent a zone.
scene::ISceneNode * zoneRoot = smgr->addEmptySceneNode();
zoneRoot->setPosition(vector3df(zoneX, zoneY, 0));
// Each zone contains a rotating cube
scene::IMeshSceneNode * node = smgr->addCubeSceneNode(15, zoneRoot);
scene::ISceneNodeAnimator * rotation = smgr->createRotationAnimator(vector3df(0.25f, 0.5f, 0.75f));
node->addAnimator(rotation);
rotation->drop();
// And each cube has three lights attached to it. The lights are attached to billboards so
// that we can see where they are. The billboards are attached to the cube, so that the
// lights are indirect descendents of the same empty scene node as the cube.
scene::IBillboardSceneNode * billboard = smgr->addBillboardSceneNode(node);
billboard->setPosition(vector3df(0, -14, 30));
billboard->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR );
billboard->setMaterialTexture(0, driver->getTexture(mediaPath + "particle.bmp"));
billboard->setMaterialFlag(video::EMF_LIGHTING, false);
smgr->addLightSceneNode(billboard, vector3df(0, 0, 0), video::SColorf(1, 0, 0), lightRadius);
billboard = smgr->addBillboardSceneNode(node);
billboard->setPosition(vector3df(-21, -14, -21));
billboard->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR );
billboard->setMaterialTexture(0, driver->getTexture(mediaPath + "particle.bmp"));
billboard->setMaterialFlag(video::EMF_LIGHTING, false);
smgr->addLightSceneNode(billboard, vector3df(0, 0, 0), video::SColorf(0, 1, 0), lightRadius);
billboard = smgr->addBillboardSceneNode(node);
billboard->setPosition(vector3df(21, -14, -21));
billboard->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR );
billboard->setMaterialTexture(0, driver->getTexture(mediaPath + "particle.bmp"));
billboard->setMaterialFlag(video::EMF_LIGHTING, false);
smgr->addLightSceneNode(billboard, vector3df(0, 0, 0), video::SColorf(0, 0, 1), lightRadius);
// Each cube also has a smaller cube rotating around it, to show that the cubes are being
// lit by the lights in their 'zone', not just lights that are their direct children.
node = smgr->addCubeSceneNode(5, node);
node->setPosition(vector3df(0, 21, 0));
}
smgr->addCameraSceneNode(0, vector3df(0,0,-130), vector3df(0,0,0));
CMyLightManager * myLightManager = new CMyLightManager(smgr);
smgr->setLightManager(0); // This is the default: we won't do light management until told to do it.
device->setEventReceiver(myLightManager);
int lastFps = -1;
while(device->run())
{
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(255,100,101,140));
smgr->drawAll();
guienv->drawAll();
driver->endScene();
int fps = driver->getFPS();
if(fps != lastFps)
{
lastFps = fps;
core::stringw str = L"Managed Lights [";
str += driver->getName();
str += "] FPS:";
str += fps;
device->setWindowCaption(str.c_str());
}
}
myLightManager->drop(); // Drop my implicit reference
device->drop();
return 0;
}
+ + +

 

+ + diff --git a/examples/21.Quake3Explorer/tutorial.html b/examples/21.Quake3Explorer/tutorial.html new file mode 100644 index 00000000..e798b49a --- /dev/null +++ b/examples/21.Quake3Explorer/tutorial.html @@ -0,0 +1,218 @@ + + + + + + + +Tutorial 21: Quake3 Explorer + + + + +
+ + + +
+
+
+
Tutorial 21: Quake3 Explorer
+
+
+
+021shot.jpg +
+

This tutorial shows how to load different Quake 3 maps.

+

Features:

    +
  • Load BSP Archives at Runtime from the menu
  • +
  • Load a Map from the menu. Showing with Screenshot
  • +
  • Set the VideoDriver at runtime from menu
  • +
  • Adjust GammaLevel at runtime
  • +
  • Create SceneNodes for the Shaders
  • +
  • Load EntityList and create Entity SceneNodes
  • +
  • Create Players with Weapons and with Collision Response
  • +
  • Play music
  • +
+

You can download the Quake III Arena demo ( copyright id software ) at the following location: ftp://ftp.idsoftware.com/idstuff/quake3/win32/q3ademo.exe

+

Copyright 2006-2011 Burningwater, Thomas Alten

#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
#include "q3factory.h"
#include "sound.h"

Game Data is used to hold Data which is needed to drive the game

struct GameData
{
GameData ( const path &startupDir) :
retVal(0), StartupDir(startupDir), createExDevice(0), Device(0)
{
setDefault ();
}
void setDefault ();
s32 save ( const path &filename );
s32 load ( const path &filename );
s32 debugState;
s32 gravityState;
s32 flyTroughState;
s32 wireFrame;
s32 guiActive;
s32 guiInputActive;
f32 GammaValue;
s32 retVal;
s32 sound;
path StartupDir;
stringw CurrentMapName;
array<path> CurrentArchiveList;
vector3df PlayerPosition;
vector3df PlayerRotation;
tQ3EntityList Variable;
Q3LevelLoadParameter loadParam;
SIrrlichtCreationParameters deviceParam;
funcptr_createDeviceEx createExDevice;
IrrlichtDevice *Device;
};

set default settings

void GameData::setDefault ()
{
debugState = EDS_OFF;
gravityState = 1;
flyTroughState = 0;
wireFrame = 0;
guiActive = 1;
guiInputActive = 0;
GammaValue = 1.f;
// default deviceParam;
#if defined ( _IRR_WINDOWS_ )
deviceParam.DriverType = EDT_DIRECT3D9;
#else
deviceParam.DriverType = EDT_OPENGL;
#endif
deviceParam.WindowSize.Width = 800;
deviceParam.WindowSize.Height = 600;
deviceParam.Fullscreen = false;
deviceParam.Bits = 24;
deviceParam.ZBufferBits = 16;
deviceParam.Vsync = false;
deviceParam.AntiAlias = false;
// default Quake3 loadParam
loadParam.defaultLightMapMaterial = EMT_LIGHTMAP;
loadParam.defaultModulate = EMFN_MODULATE_1X;
loadParam.defaultFilter = EMF_ANISOTROPIC_FILTER;
loadParam.verbose = 2;
loadParam.mergeShaderBuffer = 1; // merge meshbuffers with same material
loadParam.cleanUnResolvedMeshes = 1; // should unresolved meshes be cleaned. otherwise blue texture
loadParam.loadAllShaders = 1; // load all scripts in the script directory
loadParam.loadSkyShader = 0; // load sky Shader
loadParam.alpharef = 1;
sound = 0;
CurrentMapName = "";
CurrentArchiveList.clear ();
const io::path mediaPath = getExampleMediaPath();
// Explorer Media directory
CurrentArchiveList.push_back ( StartupDir + mediaPath );
// Add the original quake3 files before you load your custom map
// Most mods are using the original shaders, models&items&weapons
CurrentArchiveList.push_back("/q/baseq3/");
CurrentArchiveList.push_back(StartupDir + mediaPath + "map-20kdm2.pk3");
}

Load the current game State from a typical quake3 cfg file

s32 GameData::load ( const path &filename )
{
if (!Device)
return 0;
// the quake3 mesh loader can also handle *.shader and *.cfg file
IQ3LevelMesh* mesh = (IQ3LevelMesh*) Device->getSceneManager()->getMesh ( filename );
if (!mesh)
return 0;
tQ3EntityList &entityList = mesh->getEntityList ();
stringc s;
u32 pos;
for ( u32 e = 0; e != entityList.size (); ++e )
{
//dumpShader ( s, &entityList[e], false );
//printf ( s.c_str () );
for ( u32 g = 0; g != entityList[e].getGroupSize (); ++g )
{
const SVarGroup *group = entityList[e].getGroup ( g );
for ( u32 index = 0; index < group->Variable.size (); ++index )
{
const SVariable &v = group->Variable[index];
pos = 0;
if ( v.name == "playerposition" )
{
PlayerPosition = getAsVector3df ( v.content, pos );
}
else
if ( v.name == "playerrotation" )
{
PlayerRotation = getAsVector3df ( v.content, pos );
}
}
}
}
return 1;
}

Store the current game State in a quake3 configuration file

s32 GameData::save ( const path &filename )
{
return 0;
if (!Device)
return 0;
c8 buf[128];
u32 i;
// Store current Archive for restart
CurrentArchiveList.clear();
IFileSystem *fs = Device->getFileSystem();
for ( i = 0; i != fs->getFileArchiveCount(); ++i )
{
CurrentArchiveList.push_back ( fs->getFileArchive(i)->getFileList()->getPath() );
}
// Store Player Position and Rotation
ICameraSceneNode * camera = Device->getSceneManager()->getActiveCamera ();
if ( camera )
{
PlayerPosition = camera->getPosition ();
PlayerRotation = camera->getRotation ();
}
IWriteFile *file = fs->createAndWriteFile ( filename );
if (!file)
return 0;
snprintf_irr ( buf, 128, "playerposition %.f %.f %.f\nplayerrotation %.f %.f %.f\n",
PlayerPosition.X, PlayerPosition.Z, PlayerPosition.Y,
PlayerRotation.X, PlayerRotation.Z, PlayerRotation.Y);
file->write ( buf, (s32) strlen ( buf ) );
for ( i = 0; i != fs->getFileArchiveCount(); ++i )
{
snprintf_irr ( buf, 128, "archive %s\n",stringc ( fs->getFileArchive(i)->getFileList()->getPath() ).c_str () );
file->write ( buf, (s32) strlen ( buf ) );
}
file->drop ();
return 1;
}

Representing a player

struct Q3Player : public IAnimationEndCallBack
{
Q3Player ()
: Device(0), MapParent(0), Mesh(0), WeaponNode(0), StartPositionCurrent(0)
{
animation[0] = 0;
memset(Anim, 0, sizeof(TimeFire)*4);
}
virtual void OnAnimationEnd(IAnimatedMeshSceneNode* node);
void create ( IrrlichtDevice *device,
IQ3LevelMesh* mesh,
ISceneNode *mapNode,
IMetaTriangleSelector *meta
);
void shutdown ();
void setAnim ( const c8 *name );
void respawn ();
void setpos ( const vector3df &pos, const vector3df& rotation );
ISceneNodeAnimatorCollisionResponse * cam() { return camCollisionResponse ( Device ); }
IrrlichtDevice *Device;
ISceneNode* MapParent;
IQ3LevelMesh* Mesh;
IAnimatedMeshSceneNode* WeaponNode;
s32 StartPositionCurrent;
TimeFire Anim[4];
c8 animation[64];
c8 buf[64];
};

End player

void Q3Player::shutdown ()
{
setAnim ( 0 );
dropElement (WeaponNode);
if ( Device )
{
ICameraSceneNode* camera = Device->getSceneManager()->getActiveCamera();
dropElement ( camera );
Device = 0;
}
MapParent = 0;
Mesh = 0;
}

create a new player

void Q3Player::create ( IrrlichtDevice *device, IQ3LevelMesh* mesh, ISceneNode *mapNode, IMetaTriangleSelector *meta )
{
setTimeFire ( Anim + 0, 200, FIRED );
setTimeFire ( Anim + 1, 5000 );
if (!device)
return;
// load FPS weapon to Camera
Device = device;
Mesh = mesh;
MapParent = mapNode;
ISceneManager *smgr = device->getSceneManager ();
IVideoDriver * driver = device->getVideoDriver();
ICameraSceneNode* camera = 0;
core::array<SKeyMap> keyMap;
keyMap.set_used(12);
keyMap[0].Action = EKA_MOVE_FORWARD;
keyMap[0].KeyCode = KEY_UP;
keyMap[1].Action = EKA_MOVE_FORWARD;
keyMap[1].KeyCode = KEY_KEY_W;
keyMap[2].Action = EKA_MOVE_BACKWARD;
keyMap[2].KeyCode = KEY_DOWN;
keyMap[3].Action = EKA_MOVE_BACKWARD;
keyMap[3].KeyCode = KEY_KEY_S;
keyMap[4].Action = EKA_STRAFE_LEFT;
keyMap[4].KeyCode = KEY_LEFT;
keyMap[5].Action = EKA_STRAFE_LEFT;
keyMap[5].KeyCode = KEY_KEY_A;
keyMap[6].Action = EKA_STRAFE_RIGHT;
keyMap[6].KeyCode = KEY_RIGHT;
keyMap[7].Action = EKA_STRAFE_RIGHT;
keyMap[7].KeyCode = KEY_KEY_D;
keyMap[8].Action = EKA_JUMP_UP;
keyMap[8].KeyCode = KEY_KEY_J;
keyMap[9].Action = EKA_CROUCH;
keyMap[9].KeyCode = KEY_KEY_C;
keyMap[10].Action = EKA_ROTATE_LEFT;
keyMap[10].KeyCode = KEY_KEY_Q;
keyMap[11].Action = EKA_ROTATE_RIGHT;
keyMap[11].KeyCode = KEY_KEY_E;
camera = smgr->addCameraSceneNodeFPS(0, 100.0f, 0.6f, -1, keyMap.pointer(), keyMap.size(), false, 600.f);
camera->setName ( "First Person Camera" );
//camera->setFOV ( 100.f * core::DEGTORAD );
camera->setFarValue( 20000.f );
IAnimatedMeshMD2* weaponMesh = (IAnimatedMeshMD2*) smgr->getMesh("gun.md2");
if ( 0 == weaponMesh )
return;
if ( weaponMesh->getMeshType() == EAMT_MD2 )
{
s32 count = weaponMesh->getAnimationCount();
for ( s32 i = 0; i != count; ++i )
{
snprintf_irr ( buf, 64, "Animation: %s", weaponMesh->getAnimationName(i) );
device->getLogger()->log(buf, ELL_INFORMATION);
}
}
WeaponNode = smgr->addAnimatedMeshSceneNode(
weaponMesh,
smgr->getActiveCamera(),
10,
vector3df( 0, 0, 0),
vector3df(-90,-90,90)
);
WeaponNode->setMaterialFlag(EMF_LIGHTING, false);
WeaponNode->setMaterialTexture(0, driver->getTexture( "gun.jpg"));
WeaponNode->setLoopMode ( false );
WeaponNode->setName ( "tommi the gun man" );
//create a collision auto response animator
ISceneNodeAnimator* anim =
smgr->createCollisionResponseAnimator( meta, camera,
vector3df(30,45,30),
getGravity ( "earth" ),
vector3df(0,40,0),
0.0005f
);
camera->addAnimator( anim );
anim->drop();
if ( meta )
{
meta->drop ();
}
respawn ();
setAnim ( "idle" );
}

so we need a good starting Position in the level. we can ask the Quake3 Loader for all entities with class_name "info_player_deathmatch"

void Q3Player::respawn ()
{
if (!Device)
return;
ICameraSceneNode* camera = Device->getSceneManager()->getActiveCamera();
Device->getLogger()->log( "respawn" );
if (StartPositionCurrent >= Q3StartPosition(Mesh, camera,
StartPositionCurrent, cam()->getEllipsoidTranslation()))
StartPositionCurrent = 0;
else
++StartPositionCurrent;
}

set Player position from saved coordinates

void Q3Player::setpos ( const vector3df &pos, const vector3df &rotation )
{
if (!Device)
return;
Device->getLogger()->log( "setpos" );
ICameraSceneNode* camera = Device->getSceneManager()->getActiveCamera();
if ( camera )
{
camera->setPosition ( pos );
camera->setRotation ( rotation );
camera->OnAnimate ( 0 );
}
}

set the Animation of the player and weapon

void Q3Player::setAnim ( const c8 *name )
{
if ( name )
{
snprintf_irr ( animation, 64, "%s", name );
if ( WeaponNode )
{
WeaponNode->setAnimationEndCallback ( this );
WeaponNode->setMD2Animation ( animation );
}
}
else
{
animation[0] = 0;
if ( WeaponNode )
{
WeaponNode->setAnimationEndCallback ( 0 );
}
}
}
// Callback
void Q3Player::OnAnimationEnd(IAnimatedMeshSceneNode* node)
{
setAnim ( 0 );
}

GUI Elements

struct GUI
{
GUI ()
{
memset ( this, 0, sizeof ( *this ) );
}
void drop()
{
dropElement ( Window );
dropElement ( Logo );
}
IGUIComboBox* VideoDriver;
IGUIComboBox* VideoMode;
IGUICheckBox* FullScreen;
IGUICheckBox* Bit32;
IGUIScrollBar* MultiSample;
IGUIButton* SetVideoMode;
IGUIScrollBar* Tesselation;
IGUIScrollBar* Gamma;
IGUICheckBox* Collision;
IGUICheckBox* Visible_Map;
IGUICheckBox* Visible_Shader;
IGUICheckBox* Visible_Fog;
IGUICheckBox* Visible_Unresolved;
IGUICheckBox* Visible_Skydome;
IGUIButton* Respawn;
IGUITable* ArchiveList;
IGUIButton* ArchiveAdd;
IGUIButton* ArchiveRemove;
IGUIFileOpenDialog* ArchiveFileOpen;
IGUIButton* ArchiveUp;
IGUIButton* ArchiveDown;
IGUIListBox* MapList;
IGUITreeView* SceneTree;
IGUIStaticText* StatusLine;
IGUIImage* Logo;
IGUIWindow* Window;
};

CQuake3EventHandler controls the game

class CQuake3EventHandler : public IEventReceiver
{
public:
CQuake3EventHandler( GameData *gameData );
virtual ~CQuake3EventHandler ();
void Animate();
void Render();
void AddArchive ( const path& archiveName );
void LoadMap ( const stringw& mapName, s32 collision );
void CreatePlayers();
void AddSky( u32 dome, const c8 *texture );
Q3Player *GetPlayer ( u32 index ) { return &Player[index]; }
void CreateGUI();
void SetGUIActive( s32 command);
bool OnEvent(const SEvent& eve);
private:
GameData *Game;
IQ3LevelMesh* Mesh;
ISceneNode* MapParent;
ISceneNode* ShaderParent;
ISceneNode* ItemParent;
ISceneNode* UnresolvedParent;
ISceneNode* BulletParent;
ISceneNode* FogParent;
ISceneNode * SkyNode;
IMetaTriangleSelector *Meta;
c8 buf[256];
Q3Player Player[2];
struct SParticleImpact
{
u32 when;
vector3df pos;
vector3df outVector;
};
array<SParticleImpact> Impacts;
void useItem( Q3Player * player);
void createParticleImpacts( u32 now );
void createTextures ();
void addSceneTreeItem( ISceneNode * parent, IGUITreeViewNode* nodeParent);
GUI gui;
void dropMap ();
};

Constructor

CQuake3EventHandler::CQuake3EventHandler( GameData *game )
: Game(game), Mesh(0), MapParent(0), ShaderParent(0), ItemParent(0), UnresolvedParent(0),
BulletParent(0), FogParent(0), SkyNode(0), Meta(0)
{
buf[0]=0;
// Also use 16 Bit Textures for 16 Bit RenderDevice
if ( Game->deviceParam.Bits == 16 )
{
game->Device->getVideoDriver()->setTextureCreationFlag(ETCF_ALWAYS_16_BIT, true);
}
// Quake3 Shader controls Z-Writing
game->Device->getSceneManager()->getParameters()->setAttribute(scene::ALLOW_ZWRITE_ON_TRANSPARENT, true);
// create internal textures
createTextures ();
sound_init ( game->Device );
Game->Device->setEventReceiver ( this );
}
// destructor
CQuake3EventHandler::~CQuake3EventHandler ()
{
Player[0].shutdown ();
sound_shutdown ();
Game->save( "explorer.cfg" );
Game->Device->drop();
}
// create runtime textures smog, fog
void CQuake3EventHandler::createTextures()
{
IVideoDriver * driver = Game->Device->getVideoDriver();
dimension2du dim(64, 64);
video::IImage* image;
u32 i;
u32 x;
u32 y;
u32 * data;
for ( i = 0; i != 8; ++i )
{
image = driver->createImage ( video::ECF_A8R8G8B8, dim);
data = (u32*) image->getData ();
for ( y = 0; y != dim.Height; ++y )
{
for ( x = 0; x != dim.Width; ++x )
{
data [x] = 0xFFFFFFFF;
}
data = (u32*) ( (u8*) data + image->getPitch() );
}
snprintf_irr ( buf, 64, "smoke_%02d", i );
driver->addTexture( buf, image );
image->drop ();
}
// fog
for ( i = 0; i != 1; ++i )
{
image = driver->createImage ( video::ECF_A8R8G8B8, dim);
data = (u32*) image->getData ();
for ( y = 0; y != dim.Height; ++y )
{
for ( x = 0; x != dim.Width; ++x )
{
data [x] = 0xFFFFFFFF;
}
data = (u32*) ( (u8*) data + image->getPitch() );
}
snprintf_irr ( buf, 64, "fog_%02d", i );
driver->addTexture( buf, image );
image->drop ();
}
}

create the GUI

void CQuake3EventHandler::CreateGUI()
{
IGUIEnvironment *env = Game->Device->getGUIEnvironment();
IVideoDriver * driver = Game->Device->getVideoDriver();
gui.drop();
// set skin font
IGUIFont* font = env->getFont("fontlucida.png");
if (font)
env->getSkin()->setFont(font);
env->getSkin()->setColor ( EGDC_BUTTON_TEXT, video::SColor(240,0xAA,0xAA,0xAA) );
env->getSkin()->setColor ( EGDC_3D_HIGH_LIGHT, video::SColor(240,0x22,0x22,0x22) );
env->getSkin()->setColor ( EGDC_3D_FACE, video::SColor(240,0x44,0x44,0x44) );
env->getSkin()->setColor ( EGDC_EDITABLE, video::SColor(240,0x44,0x44,0x44) );
env->getSkin()->setColor ( EGDC_FOCUSED_EDITABLE, video::SColor(240,0x54,0x54,0x54) );
env->getSkin()->setColor ( EGDC_WINDOW, video::SColor(240,0x66,0x66,0x66) );
// minimal gui size 800x600
dimension2d<u32> dim ( 800, 600 );
dimension2d<u32> vdim ( Game->Device->getVideoDriver()->getScreenSize() );
if ( vdim.Height >= dim.Height && vdim.Width >= dim.Width )
{
//dim = vdim;
}
else
{
}
gui.Window = env->addWindow ( rect<s32> ( 0, 0, dim.Width, dim.Height ), false, L"Quake3 Explorer" );
gui.Window->setToolTipText ( L"Quake3Explorer. Loads and show various BSP File Format and Shaders." );
gui.Window->getCloseButton()->setToolTipText ( L"Quit Quake3 Explorer" );
// add a status line help text
gui.StatusLine = env->addStaticText( 0, rect<s32>( 5,dim.Height - 30,dim.Width - 5,dim.Height - 10),
false, false, gui.Window, -1, true
);
env->addStaticText ( L"VideoDriver:", rect<s32>( dim.Width - 400, 24, dim.Width - 310, 40 ),false, false, gui.Window, -1, false );
gui.VideoDriver = env->addComboBox(rect<s32>( dim.Width - 300, 24, dim.Width - 10, 40 ),gui.Window);
gui.VideoDriver->addItem(L"Direct3D 9.0c", EDT_DIRECT3D9 );
gui.VideoDriver->addItem(L"OpenGL 1.5", EDT_OPENGL);
gui.VideoDriver->addItem(L"Software Renderer", EDT_SOFTWARE);
gui.VideoDriver->addItem(L"Burning's Video (TM) Thomas Alten", EDT_BURNINGSVIDEO);
gui.VideoDriver->setSelected ( gui.VideoDriver->getIndexForItemData ( Game->deviceParam.DriverType ) );
gui.VideoDriver->setToolTipText ( L"Use a VideoDriver" );
env->addStaticText ( L"VideoMode:", rect<s32>( dim.Width - 400, 44, dim.Width - 310, 60 ),false, false, gui.Window, -1, false );
gui.VideoMode = env->addComboBox(rect<s32>( dim.Width - 300, 44, dim.Width - 10, 60 ),gui.Window);
gui.VideoMode->setToolTipText ( L"Supported Screenmodes" );
IVideoModeList *modeList = Game->Device->getVideoModeList();
if ( modeList )
{
s32 i;
for ( i = 0; i != modeList->getVideoModeCount (); ++i )
{
u16 d = modeList->getVideoModeDepth ( i );
if ( d < 16 )
continue;
u16 w = modeList->getVideoModeResolution ( i ).Width;
u16 h = modeList->getVideoModeResolution ( i ).Height;
u32 val = w << 16 | h;
if ( gui.VideoMode->getIndexForItemData ( val ) >= 0 )
continue;
f32 aspect = (f32) w / (f32) h;
const c8 *a = "";
if ( core::equals ( aspect, 1.3333333333f ) ) a = "4:3";
else if ( core::equals ( aspect, 1.6666666f ) ) a = "15:9 widescreen";
else if ( core::equals ( aspect, 1.7777777f ) ) a = "16:9 widescreen";
else if ( core::equals ( aspect, 1.6f ) ) a = "16:10 widescreen";
else if ( core::equals ( aspect, 2.133333f ) ) a = "20:9 widescreen";
snprintf_irr ( buf, sizeof ( buf ), "%d x %d, %s",w, h, a );
gui.VideoMode->addItem ( stringw ( buf ).c_str(), val );
}
}
gui.VideoMode->setSelected ( gui.VideoMode->getIndexForItemData (
Game->deviceParam.WindowSize.Width << 16 |
Game->deviceParam.WindowSize.Height ) );
gui.FullScreen = env->addCheckBox ( Game->deviceParam.Fullscreen, rect<s32>( dim.Width - 400, 64, dim.Width - 300, 80 ), gui.Window,-1, L"Fullscreen" );
gui.FullScreen->setToolTipText ( L"Set Fullscreen or Window Mode" );
gui.Bit32 = env->addCheckBox ( Game->deviceParam.Bits == 32, rect<s32>( dim.Width - 300, 64, dim.Width - 240, 80 ), gui.Window,-1, L"32Bit" );
gui.Bit32->setToolTipText ( L"Use 16 or 32 Bit" );
env->addStaticText ( L"MultiSample:", rect<s32>( dim.Width - 235, 64, dim.Width - 150, 80 ),false, false, gui.Window, -1, false );
gui.MultiSample = env->addScrollBar( true, rect<s32>( dim.Width - 150, 64, dim.Width - 70, 80 ), gui.Window,-1 );
gui.MultiSample->setMin ( 0 );
gui.MultiSample->setMax ( 8 );
gui.MultiSample->setSmallStep ( 1 );
gui.MultiSample->setLargeStep ( 1 );
gui.MultiSample->setPos ( Game->deviceParam.AntiAlias );
gui.MultiSample->setToolTipText ( L"Set the MultiSample (disable, 1x, 2x, 4x, 8x )" );
gui.SetVideoMode = env->addButton (rect<s32>( dim.Width - 60, 64, dim.Width - 10, 80 ), gui.Window, -1,L"set" );
gui.SetVideoMode->setToolTipText ( L"Set Video Mode with current values" );
env->addStaticText ( L"Gamma:", rect<s32>( dim.Width - 400, 104, dim.Width - 310, 120 ),false, false, gui.Window, -1, false );
gui.Gamma = env->addScrollBar( true, rect<s32>( dim.Width - 300, 104, dim.Width - 10, 120 ), gui.Window,-1 );
gui.Gamma->setMin ( 50 );
gui.Gamma->setMax ( 350 );
gui.Gamma->setSmallStep ( 1 );
gui.Gamma->setLargeStep ( 10 );
gui.Gamma->setPos ( core::floor32 ( Game->GammaValue * 100.f ) );
gui.Gamma->setToolTipText ( L"Adjust Gamma Ramp ( 0.5 - 3.5)" );
Game->Device->setGammaRamp ( Game->GammaValue, Game->GammaValue, Game->GammaValue, 0.f, 0.f );
env->addStaticText ( L"Tesselation:", rect<s32>( dim.Width - 400, 124, dim.Width - 310, 140 ),false, false, gui.Window, -1, false );
gui.Tesselation = env->addScrollBar( true, rect<s32>( dim.Width - 300, 124, dim.Width - 10, 140 ), gui.Window,-1 );
gui.Tesselation->setMin ( 2 );
gui.Tesselation->setMax ( 12 );
gui.Tesselation->setSmallStep ( 1 );
gui.Tesselation->setLargeStep ( 1 );
gui.Tesselation->setPos ( Game->loadParam.patchTesselation );
gui.Tesselation->setToolTipText ( L"How smooth should curved surfaces be rendered" );
gui.Collision = env->addCheckBox ( true, rect<s32>( dim.Width - 400, 150, dim.Width - 300, 166 ), gui.Window,-1, L"Collision" );
gui.Collision->setToolTipText ( L"Set collision on or off ( flythrough ). \nPress F7 on your Keyboard" );
gui.Visible_Map = env->addCheckBox ( true, rect<s32>( dim.Width - 300, 150, dim.Width - 240, 166 ), gui.Window,-1, L"Map" );
gui.Visible_Map->setToolTipText ( L"Show or not show the static part the Level. \nPress F3 on your Keyboard" );
gui.Visible_Shader = env->addCheckBox ( true, rect<s32>( dim.Width - 240, 150, dim.Width - 170, 166 ), gui.Window,-1, L"Shader" );
gui.Visible_Shader->setToolTipText ( L"Show or not show the Shader Nodes. \nPress F4 on your Keyboard" );
gui.Visible_Fog = env->addCheckBox ( true, rect<s32>( dim.Width - 170, 150, dim.Width - 110, 166 ), gui.Window,-1, L"Fog" );
gui.Visible_Fog->setToolTipText ( L"Show or not show the Fog Nodes. \nPress F5 on your Keyboard" );
gui.Visible_Unresolved = env->addCheckBox ( true, rect<s32>( dim.Width - 110, 150, dim.Width - 10, 166 ), gui.Window,-1, L"Unresolved" );
gui.Visible_Unresolved->setToolTipText ( L"Show the or not show the Nodes the Engine can't handle. \nPress F6 on your Keyboard" );
gui.Visible_Skydome = env->addCheckBox ( true, rect<s32>( dim.Width - 110, 180, dim.Width - 10, 196 ), gui.Window,-1, L"Skydome" );
gui.Visible_Skydome->setToolTipText ( L"Show the or not show the Skydome." );
//Respawn = env->addButton ( rect<s32>( dim.Width - 260, 90, dim.Width - 10, 106 ), 0,-1, L"Respawn" );
env->addStaticText ( L"Archives:", rect<s32>( 5, dim.Height - 530, dim.Width - 600,dim.Height - 514 ),false, false, gui.Window, -1, false );
gui.ArchiveAdd = env->addButton ( rect<s32>( dim.Width - 725, dim.Height - 530, dim.Width - 665, dim.Height - 514 ), gui.Window,-1, L"add" );
gui.ArchiveAdd->setToolTipText ( L"Add an archive, usually packed zip-archives (*.pk3) to the Filesystem" );
gui.ArchiveRemove = env->addButton ( rect<s32>( dim.Width - 660, dim.Height - 530, dim.Width - 600, dim.Height - 514 ), gui.Window,-1, L"del" );
gui.ArchiveRemove->setToolTipText ( L"Remove the selected archive from the FileSystem." );
gui.ArchiveUp = env->addButton ( rect<s32>( dim.Width - 575, dim.Height - 530, dim.Width - 515, dim.Height - 514 ), gui.Window,-1, L"up" );
gui.ArchiveUp->setToolTipText ( L"Arrange Archive Look-up Hirachy. Move the selected Archive up" );
gui.ArchiveDown = env->addButton ( rect<s32>( dim.Width - 510, dim.Height - 530, dim.Width - 440, dim.Height - 514 ), gui.Window,-1, L"down" );
gui.ArchiveDown->setToolTipText ( L"Arrange Archive Look-up Hirachy. Move the selected Archive down" );
gui.ArchiveList = env->addTable ( rect<s32>( 5,dim.Height - 510, dim.Width - 450,dim.Height - 410 ), gui.Window );
gui.ArchiveList->addColumn ( L"Type", 0 );
gui.ArchiveList->addColumn ( L"Real File Path", 1 );
gui.ArchiveList->setColumnWidth ( 0, 60 );
gui.ArchiveList->setColumnWidth ( 1, 284 );
gui.ArchiveList->setToolTipText ( L"Show the attached Archives" );
env->addStaticText ( L"Maps:", rect<s32>( 5, dim.Height - 400, dim.Width - 450,dim.Height - 380 ),false, false, gui.Window, -1, false );
gui.MapList = env->addListBox ( rect<s32>( 5,dim.Height - 380, dim.Width - 450,dim.Height - 40 ), gui.Window, -1, true );
gui.MapList->setToolTipText ( L"Show the current Maps in all Archives.\n Double-Click the Map to start the level" );
// create a visible Scene Tree
env->addStaticText ( L"Scenegraph:", rect<s32>( dim.Width - 400, dim.Height - 400, dim.Width - 5,dim.Height - 380 ),false, false, gui.Window, -1, false );
gui.SceneTree = env->addTreeView( rect<s32>( dim.Width - 400, dim.Height - 380, dim.Width - 5, dim.Height - 40 ),
gui.Window, -1, true, true, false );
gui.SceneTree->setToolTipText ( L"Show the current Scenegraph" );
gui.SceneTree->getRoot()->clearChildren();
addSceneTreeItem ( Game->Device->getSceneManager()->getRootSceneNode(), gui.SceneTree->getRoot() );
IGUIImageList* imageList = env->createImageList( driver->getTexture ( "iconlist.png" ),
dimension2di( 32, 32 ), true );
if ( imageList )
{
gui.SceneTree->setImageList( imageList );
imageList->drop ();
}
// load the engine logo
gui.Logo = env->addImage( driver->getTexture("irrlichtlogo3.png"), position2d<s32>(5, 16 ), true, 0 );
gui.Logo->setToolTipText ( L"The great Irrlicht Engine" );
AddArchive ( "" );
}

Add an Archive to the FileSystems and updates the GUI

void CQuake3EventHandler::AddArchive ( const path& archiveName )
{
IFileSystem *fs = Game->Device->getFileSystem();
u32 i;
if ( archiveName.size () )
{
bool exists = false;
for ( i = 0; i != fs->getFileArchiveCount(); ++i )
{
if ( fs->getFileArchive(i)->getFileList()->getPath() == archiveName )
{
exists = true;
break;
}
}
if (!exists)
{
fs->addFileArchive(archiveName, true, false);
}
}
// store the current archives in game data
// show the attached Archive in proper order
if ( gui.ArchiveList )
{
gui.ArchiveList->clearRows();
for ( i = 0; i != fs->getFileArchiveCount(); ++i )
{
IFileArchive * archive = fs->getFileArchive ( i );
u32 index = gui.ArchiveList->addRow(i);
core::stringw typeName;
switch(archive->getType())
{
case io::EFAT_ZIP:
typeName = "ZIP";
break;
case io::EFAT_GZIP:
typeName = "gzip";
break;
case io::EFAT_FOLDER:
typeName = "Mount";
break;
case io::EFAT_PAK:
typeName = "PAK";
break;
case io::EFAT_TAR:
typeName = "TAR";
break;
default:
typeName = "archive";
}
gui.ArchiveList->setCellText ( index, 0, typeName );
gui.ArchiveList->setCellText ( index, 1, archive->getFileList()->getPath() );
}
}
// browse the archives for maps
if ( gui.MapList )
{
gui.MapList->clear();
IGUISpriteBank *bank = Game->Device->getGUIEnvironment()->getSpriteBank("sprite_q3map");
if ( 0 == bank )
bank = Game->Device->getGUIEnvironment()->addEmptySpriteBank("sprite_q3map");
SGUISprite sprite;
SGUISpriteFrame frame;
core::rect<s32> r;
bank->getSprites().clear();
bank->getPositions().clear ();
gui.MapList->setSpriteBank ( bank );
u32 g = 0;
core::stringw s;
// browse the attached file system
fs->setFileListSystem ( FILESYSTEM_VIRTUAL );
fs->changeWorkingDirectoryTo ( "/maps/" );
IFileList *fileList = fs->createFileList ();
fs->setFileListSystem ( FILESYSTEM_NATIVE );
for ( i=0; i< fileList->getFileCount(); ++i)
{
s = fileList->getFullFileName(i);
if ( s.find ( ".bsp" ) >= 0 )
{
// get level screenshot. reformat texture to 128x128
path c ( s );
deletePathFromFilename ( c );
cutFilenameExtension ( c, c );
c = path ( "levelshots/" ) + c;
dimension2du dim ( 128, 128 );
IVideoDriver * driver = Game->Device->getVideoDriver();
IImage* image = 0;
ITexture *tex = 0;
path filename;
filename = c + ".jpg";
if ( fs->existFile ( filename ) )
image = driver->createImageFromFile( filename );
if ( 0 == image )
{
filename = c + ".tga";
if ( fs->existFile ( filename ) )
image = driver->createImageFromFile( filename );
}
if ( image )
{
IImage* filter = driver->createImage ( video::ECF_R8G8B8, dim );
image->copyToScalingBoxFilter ( filter, 0 );
image->drop ();
image = filter;
}
if ( image )
{
tex = driver->addTexture ( filename, image );
image->drop ();
}
bank->setTexture ( g, tex );
r.LowerRightCorner.X = dim.Width;
r.LowerRightCorner.Y = dim.Height;
gui.MapList->setItemHeight ( r.LowerRightCorner.Y + 4 );
frame.rectNumber = bank->getPositions().size();
frame.textureNumber = g;
bank->getPositions().push_back(r);
sprite.Frames.set_used ( 0 );
sprite.Frames.push_back(frame);
sprite.frameTime = 0;
bank->getSprites().push_back(sprite);
gui.MapList->addItem ( s.c_str (), g );
g += 1;
}
}
fileList->drop ();
gui.MapList->setSelected ( -1 );
IGUIScrollBar * bar = (IGUIScrollBar*)gui.MapList->getElementFromId( 0 );
if ( bar )
bar->setPos ( 0 );
}
}

clears the Map in Memory

void CQuake3EventHandler::dropMap ()
{
IVideoDriver * driver = Game->Device->getVideoDriver();
driver->removeAllHardwareBuffers ();
driver->removeAllTextures ();
Player[0].shutdown ();
dropElement ( ItemParent );
dropElement ( ShaderParent );
dropElement ( UnresolvedParent );
dropElement ( FogParent );
dropElement ( BulletParent );
Impacts.clear();
if ( Meta )
{
Meta = 0;
}
dropElement ( MapParent );
dropElement ( SkyNode );
// clean out meshes, because textures are invalid
// TODO: better texture handling;-)
IMeshCache *cache = Game->Device->getSceneManager ()->getMeshCache();
cache->clear ();
Mesh = 0;
}

Load new map

void CQuake3EventHandler::LoadMap ( const stringw &mapName, s32 collision )
{
if ( 0 == mapName.size() )
return;
dropMap ();
IFileSystem *fs = Game->Device->getFileSystem();
ISceneManager *smgr = Game->Device->getSceneManager ();
IReadFile* file = fs->createMemoryReadFile(&Game->loadParam,
sizeof(Game->loadParam), L"levelparameter.cfg", false);
// load cfg file
smgr->getMesh( file );
file->drop ();
// load the actual map
Mesh = (IQ3LevelMesh*) smgr->getMesh(mapName);
if ( 0 == Mesh )
return;

add the geometry mesh to the Scene ( polygon & patches ) The Geometry mesh is optimised for faster drawing

IMesh *geometry = Mesh->getMesh(E_Q3_MESH_GEOMETRY);
if ( 0 == geometry || geometry->getMeshBufferCount() == 0)
return;
Game->CurrentMapName = mapName;
//create a collision list
Meta = 0;
ITriangleSelector * selector = 0;
if (collision)
Meta = smgr->createMetaTriangleSelector();
//IMeshBuffer *b0 = geometry->getMeshBuffer(0);
//s32 minimalNodes = b0 ? core::s32_max ( 2048, b0->getVertexCount() / 32 ) : 2048;
s32 minimalNodes = 2048;
MapParent = smgr->addOctreeSceneNode(geometry, 0, -1, minimalNodes);
MapParent->setName ( mapName );
if ( Meta )
{
selector = smgr->createOctreeTriangleSelector( geometry,MapParent, minimalNodes);
//selector = smgr->createTriangleSelector ( geometry, MapParent );
Meta->addTriangleSelector( selector);
selector->drop ();
}
// logical parent for the items
ItemParent = smgr->addEmptySceneNode();
if ( ItemParent )
ItemParent->setName ( "Item Container" );
ShaderParent = smgr->addEmptySceneNode();
if ( ShaderParent )
ShaderParent->setName ( "Shader Container" );
UnresolvedParent = smgr->addEmptySceneNode();
if ( UnresolvedParent )
UnresolvedParent->setName ( "Unresolved Container" );
FogParent = smgr->addEmptySceneNode();
if ( FogParent )
FogParent->setName ( "Fog Container" );
// logical parent for the bullets
BulletParent = smgr->addEmptySceneNode();
if ( BulletParent )
BulletParent->setName ( "Bullet Container" );

now construct SceneNodes for each Shader The Objects are stored in the quake mesh E_Q3_MESH_ITEMS and the Shader ID is stored in the MaterialParameters mostly dark looking skulls and moving lava.. or green flashing tubes?

Q3ShaderFactory ( Game->loadParam, Game->Device, Mesh, E_Q3_MESH_ITEMS,ShaderParent, Meta, false );
Q3ShaderFactory ( Game->loadParam, Game->Device, Mesh, E_Q3_MESH_FOG,FogParent, 0, false );
Q3ShaderFactory ( Game->loadParam, Game->Device, Mesh, E_Q3_MESH_UNRESOLVED,UnresolvedParent, Meta, true );

Now construct Models from Entity List

Q3ModelFactory ( Game->loadParam, Game->Device, Mesh, ItemParent, false );
}

Adds a SceneNode with an icon to the Scene Tree

void CQuake3EventHandler::addSceneTreeItem( ISceneNode * parent, IGUITreeViewNode* nodeParent)
{
IGUITreeViewNode* node;
wchar_t msg[128];
s32 imageIndex;
list<ISceneNode*>::ConstIterator it = parent->getChildren().begin();
for (; it != parent->getChildren().end(); ++it)
{
switch ( (*it)->getType () )
{
case ESNT_Q3SHADER_SCENE_NODE: imageIndex = 0; break;
case ESNT_CAMERA: imageIndex = 1; break;
case ESNT_EMPTY: imageIndex = 2; break;
case ESNT_MESH: imageIndex = 3; break;
case ESNT_OCTREE: imageIndex = 3; break;
case ESNT_ANIMATED_MESH: imageIndex = 4; break;
case ESNT_SKY_BOX: imageIndex = 5; break;
case ESNT_BILLBOARD: imageIndex = 6; break;
case ESNT_PARTICLE_SYSTEM: imageIndex = 7; break;
case ESNT_TEXT: imageIndex = 8; break;
default:imageIndex = -1; break;
}
if ( imageIndex < 0 )
{
swprintf_irr ( msg, 128, L"%hs,%hs",
Game->Device->getSceneManager ()->getSceneNodeTypeName ( (*it)->getType () ),
(*it)->getName()
);
}
else
{
swprintf_irr ( msg, 128, L"%hs",(*it)->getName() );
}
node = nodeParent->addChildBack( msg, 0, imageIndex );
// Add all Animators
list<ISceneNodeAnimator*>::ConstIterator ait = (*it)->getAnimators().begin();
for (; ait != (*it)->getAnimators().end(); ++ait)
{
imageIndex = -1;
swprintf_irr ( msg, 128, L"%hs",
Game->Device->getSceneManager ()->getAnimatorTypeName ( (*ait)->getType () )
);
switch ( (*ait)->getType () )
{
case ESNAT_FLY_CIRCLE:
case ESNAT_FLY_STRAIGHT:
case ESNAT_FOLLOW_SPLINE:
case ESNAT_ROTATION:
case ESNAT_TEXTURE:
case ESNAT_DELETION:
case ESNAT_COLLISION_RESPONSE:
case ESNAT_CAMERA_FPS:
case ESNAT_CAMERA_MAYA:
default:
break;
}
node->addChildBack( msg, 0, imageIndex );
}
addSceneTreeItem ( *it, node );
}
}
// Adds life!
void CQuake3EventHandler::CreatePlayers()
{
Player[0].create ( Game->Device, Mesh, MapParent, Meta );
}
// Adds a skydome to the scene
void CQuake3EventHandler::AddSky( u32 dome, const c8 *texture)
{
ISceneManager *smgr = Game->Device->getSceneManager ();
IVideoDriver * driver = Game->Device->getVideoDriver();
bool oldMipMapState = driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS);
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
if ( 0 == dome )
{
// irrlicht order
//static const c8*p[] = { "ft", "lf", "bk", "rt", "up", "dn" };
// quake3 order
static const c8*p[] = { "ft", "rt", "bk", "lf", "up", "dn" };
u32 i = 0;
snprintf_irr ( buf, 64, "%s_%s.jpg", texture, p[i] );
SkyNode = smgr->addSkyBoxSceneNode( driver->getTexture ( buf ), 0, 0, 0, 0, 0 );
if (SkyNode)
{
for ( i = 0; i < 6; ++i )
{
snprintf_irr ( buf, 64, "%s_%s.jpg", texture, p[i] );
SkyNode->getMaterial(i).setTexture ( 0, driver->getTexture ( buf ) );
}
}
}
else
if ( 1 == dome )
{
snprintf_irr ( buf, 64, "%s.jpg", texture );
SkyNode = smgr->addSkyDomeSceneNode(
driver->getTexture( buf ), 32,32,
1.f, 1.f, 1000.f, 0, 11);
}
else
if ( 2 == dome )
{
snprintf_irr ( buf, 64, "%s.jpg", texture );
SkyNode = smgr->addSkyDomeSceneNode(
driver->getTexture( buf ), 16,8,
0.95f, 2.f, 1000.f, 0, 11);
}
if (SkyNode)
SkyNode->setName("Skydome");
//SkyNode->getMaterial(0).ZBuffer = video::EMDF_DEPTH_LESS_EQUAL;
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, oldMipMapState);
}
// enable GUI elements
void CQuake3EventHandler::SetGUIActive( s32 command)
{
bool inputState = false;
ICameraSceneNode * camera = Game->Device->getSceneManager()->getActiveCamera ();
switch ( command )
{
case 0: Game->guiActive = 0; inputState = !Game->guiActive; break;
case 1: Game->guiActive = 1; inputState = !Game->guiActive;;break;
case 2: Game->guiActive ^= 1; inputState = !Game->guiActive;break;
case 3:
if ( camera )
inputState = !camera->isInputReceiverEnabled();
break;
}
if ( camera )
{
camera->setInputReceiverEnabled ( inputState );
Game->Device->getCursorControl()->setVisible( !inputState );
}
if ( gui.Window )
{
gui.Window->setVisible ( Game->guiActive != 0 );
}
if ( Game->guiActive &&
gui.SceneTree && Game->Device->getGUIEnvironment()->getFocus() != gui.SceneTree
)
{
gui.SceneTree->getRoot()->clearChildren();
addSceneTreeItem ( Game->Device->getSceneManager()->getRootSceneNode(), gui.SceneTree->getRoot() );
}
Game->Device->getGUIEnvironment()->setFocus ( Game->guiActive ? gui.Window: 0 );
}

Handle game input

bool CQuake3EventHandler::OnEvent(const SEvent& eve)
{
if ( eve.EventType == EET_LOG_TEXT_EVENT )
{
return false;
}
if ( Game->guiActive && eve.EventType == EET_GUI_EVENT )
{
if ( eve.GUIEvent.Caller == gui.MapList && eve.GUIEvent.EventType == gui::EGET_LISTBOX_SELECTED_AGAIN )
{
s32 selected = gui.MapList->getSelected();
if ( selected >= 0 )
{
stringw loadMap = gui.MapList->getListItem ( selected );
if ( 0 == MapParent || loadMap != Game->CurrentMapName )
{
printf ( "Loading map %ls\n", loadMap.c_str() );
LoadMap ( loadMap , 1 );
if ( 0 == Game->loadParam.loadSkyShader )
{
AddSky ( 1, "skydome2" );
}
CreatePlayers ();
CreateGUI ();
SetGUIActive ( 0 );
return true;
}
}
}
else
if ( eve.GUIEvent.Caller == gui.ArchiveRemove && eve.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED )
{
Game->Device->getFileSystem()->removeFileArchive( gui.ArchiveList->getSelected() );
Game->CurrentMapName = "";
AddArchive ( "" );
}
else
if ( eve.GUIEvent.Caller == gui.ArchiveAdd && eve.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED )
{
if ( 0 == gui.ArchiveFileOpen )
{
Game->Device->getFileSystem()->setFileListSystem ( FILESYSTEM_NATIVE );
gui.ArchiveFileOpen = Game->Device->getGUIEnvironment()->addFileOpenDialog ( L"Add Game Archive" , false,gui.Window );
}
}
else
if ( eve.GUIEvent.Caller == gui.ArchiveFileOpen && eve.GUIEvent.EventType == gui::EGET_FILE_SELECTED )
{
AddArchive ( gui.ArchiveFileOpen->getFileNameP() );
gui.ArchiveFileOpen = 0;
}
else
if ( eve.GUIEvent.Caller == gui.ArchiveFileOpen && eve.GUIEvent.EventType == gui::EGET_DIRECTORY_SELECTED )
{
AddArchive ( gui.ArchiveFileOpen->getDirectoryName() );
}
else
if ( eve.GUIEvent.Caller == gui.ArchiveFileOpen && eve.GUIEvent.EventType == gui::EGET_FILE_CHOOSE_DIALOG_CANCELLED )
{
gui.ArchiveFileOpen = 0;
}
else
if ( ( eve.GUIEvent.Caller == gui.ArchiveUp || eve.GUIEvent.Caller == gui.ArchiveDown ) &&
eve.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED )
{
s32 rel = eve.GUIEvent.Caller == gui.ArchiveUp ? -1 : 1;
if ( Game->Device->getFileSystem()->moveFileArchive ( gui.ArchiveList->getSelected (), rel ) )
{
s32 newIndex = core::s32_clamp ( gui.ArchiveList->getSelected() + rel, 0, gui.ArchiveList->getRowCount() - 1 );
AddArchive ( "" );
gui.ArchiveList->setSelected ( newIndex );
Game->CurrentMapName = "";
}
}
else
if ( eve.GUIEvent.Caller == gui.VideoDriver && eve.GUIEvent.EventType == gui::EGET_COMBO_BOX_CHANGED )
{
Game->deviceParam.DriverType = (E_DRIVER_TYPE) gui.VideoDriver->getItemData ( gui.VideoDriver->getSelected() );
}
else
if ( eve.GUIEvent.Caller == gui.VideoMode && eve.GUIEvent.EventType == gui::EGET_COMBO_BOX_CHANGED )
{
u32 val = gui.VideoMode->getItemData ( gui.VideoMode->getSelected() );
Game->deviceParam.WindowSize.Width = val >> 16;
Game->deviceParam.WindowSize.Height = val & 0xFFFF;
}
else
if ( eve.GUIEvent.Caller == gui.FullScreen && eve.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED )
{
Game->deviceParam.Fullscreen = gui.FullScreen->isChecked();
}
else
if ( eve.GUIEvent.Caller == gui.Bit32 && eve.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED )
{
Game->deviceParam.Bits = gui.Bit32->isChecked() ? 32 : 16;
}
else
if ( eve.GUIEvent.Caller == gui.MultiSample && eve.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED )
{
Game->deviceParam.AntiAlias = gui.MultiSample->getPos();
}
else
if ( eve.GUIEvent.Caller == gui.Tesselation && eve.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED )
{
Game->loadParam.patchTesselation = gui.Tesselation->getPos ();
}
else
if ( eve.GUIEvent.Caller == gui.Gamma && eve.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED )
{
Game->GammaValue = gui.Gamma->getPos () * 0.01f;
Game->Device->setGammaRamp ( Game->GammaValue, Game->GammaValue, Game->GammaValue, 0.f, 0.f );
}
else
if ( eve.GUIEvent.Caller == gui.SetVideoMode && eve.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED )
{
Game->retVal = 2;
Game->Device->closeDevice();
}
else
if ( eve.GUIEvent.Caller == gui.Window && eve.GUIEvent.EventType == gui::EGET_ELEMENT_CLOSED )
{
Game->Device->closeDevice();
}
else
if ( eve.GUIEvent.Caller == gui.Collision && eve.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED )
{
// set fly through active
Game->flyTroughState ^= 1;
Player[0].cam()->setAnimateTarget ( Game->flyTroughState == 0 );
printf ( "collision %d\n", Game->flyTroughState == 0 );
}
else
if ( eve.GUIEvent.Caller == gui.Visible_Map && eve.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED )
{
bool v = gui.Visible_Map->isChecked();
if ( MapParent )
{
printf ( "static node set visible %d\n",v );
MapParent->setVisible ( v );
}
}
else
if ( eve.GUIEvent.Caller == gui.Visible_Shader && eve.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED )
{
bool v = gui.Visible_Shader->isChecked();
if ( ShaderParent )
{
printf ( "shader node set visible %d\n",v );
ShaderParent->setVisible ( v );
}
}
else
if ( eve.GUIEvent.Caller == gui.Visible_Skydome && eve.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED )
{
if ( SkyNode )
{
bool v = !SkyNode->isVisible();
printf ( "skynode set visible %d\n",v );
SkyNode->setVisible ( v );
}
}
else
if ( eve.GUIEvent.Caller == gui.Respawn && eve.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED )
{
Player[0].respawn ();
}
return false;
}
// fire
if ((eve.EventType == EET_KEY_INPUT_EVENT && eve.KeyInput.Key == KEY_SPACE &&
eve.KeyInput.PressedDown == false) ||
(eve.EventType == EET_MOUSE_INPUT_EVENT && eve.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
)
{
ICameraSceneNode * camera = Game->Device->getSceneManager()->getActiveCamera ();
if ( camera && camera->isInputReceiverEnabled () )
{
useItem( Player + 0 );
}
}
// gui active
if ((eve.EventType == EET_KEY_INPUT_EVENT && eve.KeyInput.Key == KEY_F1 &&
eve.KeyInput.PressedDown == false) ||
(eve.EventType == EET_MOUSE_INPUT_EVENT && eve.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
)
{
SetGUIActive ( 2 );
}
// check if user presses the key
if ( eve.EventType == EET_KEY_INPUT_EVENT && eve.KeyInput.PressedDown == false)
{
// Escape toggles camera Input
if ( eve.KeyInput.Key == irr::KEY_ESCAPE )
{
SetGUIActive ( 3 );
}
else
if (eve.KeyInput.Key == KEY_F11)
{
// screenshot are taken without gamma!
IImage* image = Game->Device->getVideoDriver()->createScreenShot();
if (image)
{
core::vector3df pos;
core::vector3df rot;
ICameraSceneNode * cam = Game->Device->getSceneManager()->getActiveCamera ();
if ( cam )
{
pos = cam->getPosition ();
rot = cam->getRotation ();
}
snprintf_irr(buf, 256, "%s_%ls_%.0f_%.0f_%.0f_%.0f_%.0f_%.0f.jpg",
DRIVER_TYPE_NAMES_SHORT[Game->Device->getVideoDriver()->getDriverType()],
Game->CurrentMapName.c_str(),
pos.X, pos.Y, pos.Z,
rot.X, rot.Y, rot.Z
);
path filename ( buf );
filename.replace ( '/', '_' );
printf ( "screenshot : %s\n", filename.c_str() );
Game->Device->getVideoDriver()->writeImageToFile(image, filename, 100 );
image->drop();
}
}
else
if (eve.KeyInput.Key == KEY_F9)
{
s32 value = EDS_OFF;
Game->debugState = ( Game->debugState + 1 ) & 3;
switch ( Game->debugState )
{
case 1: value = EDS_NORMALS | EDS_MESH_WIRE_OVERLAY | EDS_BBOX_ALL; break;
case 2: value = EDS_NORMALS | EDS_MESH_WIRE_OVERLAY | EDS_SKELETON; break;
}

set debug map data on/off debugState = debugState == EDS_OFF ? EDS_NORMALS | EDS_MESH_WIRE_OVERLAY | EDS_BBOX_ALL: EDS_OFF;

if ( ItemParent )
{
list<ISceneNode*>::ConstIterator it = ItemParent->getChildren().begin();
for (; it != ItemParent->getChildren().end(); ++it)
{
(*it)->setDebugDataVisible ( value );
}
}
if ( ShaderParent )
{
list<ISceneNode*>::ConstIterator it = ShaderParent->getChildren().begin();
for (; it != ShaderParent->getChildren().end(); ++it)
{
(*it)->setDebugDataVisible ( value );
}
}
if ( UnresolvedParent )
{
list<ISceneNode*>::ConstIterator it = UnresolvedParent->getChildren().begin();
for (; it != UnresolvedParent->getChildren().end(); ++it)
{
(*it)->setDebugDataVisible ( value );
}
}
if ( FogParent )
{
list<ISceneNode*>::ConstIterator it = FogParent->getChildren().begin();
for (; it != FogParent->getChildren().end(); ++it)
{
(*it)->setDebugDataVisible ( value );
}
}
if ( SkyNode )
{
SkyNode->setDebugDataVisible ( value );
}
}
else
if (eve.KeyInput.Key == KEY_F8)
{
// set gravity on/off
Game->gravityState ^= 1;
Player[0].cam()->setGravity ( getGravity ( Game->gravityState ? "earth" : "none" ) );
printf ( "gravity %s\n", Game->gravityState ? "earth" : "none" );
}
else
if (eve.KeyInput.Key == KEY_F7)
{
// set fly through active
Game->flyTroughState ^= 1;
Player[0].cam()->setAnimateTarget ( Game->flyTroughState == 0 );
if ( gui.Collision )
gui.Collision->setChecked ( Game->flyTroughState == 0 );
printf ( "collision %d\n", Game->flyTroughState == 0 );
}
else
if (eve.KeyInput.Key == KEY_F2)
{
Player[0].respawn ();
}
else
if (eve.KeyInput.Key == KEY_F3)
{
if ( MapParent )
{
bool v = !MapParent->isVisible ();
printf ( "static node set visible %d\n",v );
MapParent->setVisible ( v );
if ( gui.Visible_Map )
gui.Visible_Map->setChecked ( v );
}
}
else
if (eve.KeyInput.Key == KEY_F4)
{
if ( ShaderParent )
{
bool v = !ShaderParent->isVisible ();
printf ( "shader node set visible %d\n",v );
ShaderParent->setVisible ( v );
if ( gui.Visible_Shader )
gui.Visible_Shader->setChecked ( v );
}
}
else
if (eve.KeyInput.Key == KEY_F5)
{
if ( FogParent )
{
bool v = !FogParent->isVisible ();
printf ( "fog node set visible %d\n",v );
FogParent->setVisible ( v );
if ( gui.Visible_Fog )
gui.Visible_Fog->setChecked ( v );
}
}
else
if (eve.KeyInput.Key == KEY_F6)
{
if ( UnresolvedParent )
{
bool v = !UnresolvedParent->isVisible ();
printf ( "unresolved node set visible %d\n",v );
UnresolvedParent->setVisible ( v );
if ( gui.Visible_Unresolved )
gui.Visible_Unresolved->setChecked ( v );
}
}
}
// check if user presses the key C ( for crouch)
if ( eve.EventType == EET_KEY_INPUT_EVENT && eve.KeyInput.Key == KEY_KEY_C )
{
// crouch
ISceneNodeAnimatorCollisionResponse *anim = Player[0].cam ();
if ( anim && 0 == Game->flyTroughState )
{
if ( false == eve.KeyInput.PressedDown )
{
// stand up
anim->setEllipsoidRadius ( vector3df(30,45,30) );
anim->setEllipsoidTranslation ( vector3df(0,40,0));
}
else
{
// on your knees
anim->setEllipsoidRadius ( vector3df(30,20,30) );
anim->setEllipsoidTranslation ( vector3df(0,20,0));
}
return true;
}
}
return false;
}

useItem

void CQuake3EventHandler::useItem( Q3Player * player)
{
ISceneManager* smgr = Game->Device->getSceneManager();
ICameraSceneNode* camera = smgr->getActiveCamera();
if (!camera)
return;
SParticleImpact imp;
imp.when = 0;
// get line of camera
vector3df start = camera->getPosition();
if ( player->WeaponNode )
{
start.X += 0.f;
start.Y += 0.f;
start.Z += 0.f;
}
vector3df end = (camera->getTarget() - start);
end.normalize();
start += end*20.0f;
end = start + (end * camera->getFarValue());
triangle3df triangle;
line3d<f32> line(start, end);
// get intersection point with map
scene::ISceneNode* hitNode;
if (smgr->getSceneCollisionManager()->getCollisionPoint(
line, Meta, end, triangle,hitNode))
{
// collides with wall
vector3df out = triangle.getNormal();
out.setLength(0.03f);
imp.when = 1;
imp.outVector = out;
imp.pos = end;
player->setAnim ( "pow" );
player->Anim[1].next += player->Anim[1].delta;
}
else
{
// doesnt collide with wall
vector3df start = camera->getPosition();
if ( player->WeaponNode )
{
//start.X += 10.f;
//start.Y += -5.f;
//start.Z += 1.f;
}
vector3df end = (camera->getTarget() - start);
end.normalize();
start += end*20.0f;
end = start + (end * camera->getFarValue());
}
// create fire ball
ISceneNode* node = 0;
node = smgr->addBillboardSceneNode( BulletParent,dimension2d<f32>(10,10), start);
node->setMaterialFlag(EMF_LIGHTING, false);
node->setMaterialTexture(0, Game->Device->getVideoDriver()->getTexture("fireball.bmp"));
node->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
node->setMaterialType(EMT_TRANSPARENT_ADD_COLOR);
f32 length = (f32)(end - start).getLength();
const f32 speed = 5.8f;
u32 time = (u32)(length / speed);
ISceneNodeAnimator* anim = 0;
// set flight line
anim = smgr->createFlyStraightAnimator(start, end, time);
node->addAnimator(anim);
anim->drop();
snprintf_irr ( buf, 64, "bullet: %s on %.1f,%1.f,%1.f",
imp.when ? "hit" : "nohit", end.X, end.Y, end.Z );
node->setName ( buf );
anim = smgr->createDeleteAnimator(time);
node->addAnimator(anim);
anim->drop();
if (imp.when)
{
// create impact note
imp.when = Game->Device->getTimer()->getTime() +
(time + (s32) ( ( 1.f + Noiser::get() ) * 250.f ));
Impacts.push_back(imp);
}
// play sound
}
// rendered when bullets hit something
void CQuake3EventHandler::createParticleImpacts( u32 now )
{
ISceneManager* sm = Game->Device->getSceneManager();
struct smokeLayer
{
const c8 * texture;
f32 scale;
f32 minparticleSize;
f32 maxparticleSize;
f32 boxSize;
u32 minParticle;
u32 maxParticle;
u32 fadeout;
u32 lifetime;
};
smokeLayer smoke[] =
{
{ "smoke2.jpg", 0.4f, 1.5f, 18.f, 20.f, 20, 50, 2000, 10000 },
{ "smoke3.jpg", 0.2f, 1.2f, 15.f, 20.f, 10, 30, 1000, 12000 }
};
u32 i;
u32 g;
s32 factor = 1;
for ( g = 0; g != 2; ++g )
{
smoke[g].minParticle *= factor;
smoke[g].maxParticle *= factor;
smoke[g].lifetime *= factor;
smoke[g].boxSize *= Noiser::get() * 0.5f;
}
for ( i=0; i < Impacts.size(); ++i)
{
if (now < Impacts[i].when)
continue;
// create smoke particle system
IParticleSystemSceneNode* pas = 0;
for ( g = 0; g != 2; ++g )
{
pas = sm->addParticleSystemSceneNode(false, BulletParent, -1, Impacts[i].pos);
snprintf_irr ( buf, 64, "bullet impact smoke at %.1f,%.1f,%1.f",
Impacts[i].pos.X,Impacts[i].pos.Y,Impacts[i].pos.Z);
pas->setName ( buf );
// create a flat smoke
vector3df direction = Impacts[i].outVector;
direction *= smoke[g].scale;
IParticleEmitter* em = pas->createBoxEmitter(
aabbox3d<f32>(-4.f,0.f,-4.f,20.f,smoke[g].minparticleSize,20.f),
direction,smoke[g].minParticle, smoke[g].maxParticle,
video::SColor(0,0,0,0),video::SColor(0,128,128,128),
250,4000, 60);
em->setMinStartSize (dimension2d<f32>( smoke[g].minparticleSize, smoke[g].minparticleSize));
em->setMaxStartSize (dimension2d<f32>( smoke[g].maxparticleSize, smoke[g].maxparticleSize));
pas->setEmitter(em);
em->drop();
// particles get invisible
IParticleAffector* paf = pas->createFadeOutParticleAffector(
video::SColor ( 0, 0, 0, 0 ), smoke[g].fadeout);
pas->addAffector(paf);
paf->drop();
// particle system life time
ISceneNodeAnimator* anim = sm->createDeleteAnimator( smoke[g].lifetime);
pas->addAnimator(anim);
anim->drop();
pas->setMaterialFlag(video::EMF_LIGHTING, false);
pas->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
pas->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR );
pas->setMaterialTexture(0, Game->Device->getVideoDriver()->getTexture( smoke[g].texture ));
}
// play impact sound
#ifdef USE_IRRKLANG

if (irrKlang) { audio::ISound* sound = irrKlang->play3D(impactSound, Impacts[i].pos, false, false, true);

+

if (sound) { adjust max value a bit to make to sound of an impact louder sound->setMinDistance(400); sound->drop(); } }

#endif
// delete entry
Impacts.erase(i);
i--;
}
}

render

void CQuake3EventHandler::Render()
{
IVideoDriver * driver = Game->Device->getVideoDriver();
if ( 0 == driver )
return;
// TODO: This does not work, yet.
const bool anaglyph=false;
if (anaglyph)
{
scene::ICameraSceneNode* cameraOld = Game->Device->getSceneManager()->getActiveCamera();
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, SColor(0,0,0,0));
driver->getOverrideMaterial().Material.ColorMask = ECP_NONE;
driver->getOverrideMaterial().EnableFlags = EMF_COLOR_MASK;
driver->getOverrideMaterial().EnablePasses = ESNRP_SKY_BOX +
ESNRP_SOLID +
ESNRP_TRANSPARENT +
ESNRP_TRANSPARENT_EFFECT +
ESNRP_SHADOW;
Game->Device->getSceneManager()->drawAll();
driver->clearBuffers(video::ECBF_DEPTH, video::SColor(255,0,0,0));
const vector3df oldPosition = cameraOld->getPosition();
const vector3df oldTarget = cameraOld->getTarget();
const matrix4 startMatrix = cameraOld->getAbsoluteTransformation();
const vector3df focusPoint = (oldTarget -
cameraOld->getAbsolutePosition()).setLength(10000) +
cameraOld->getAbsolutePosition() ;
scene::ICameraSceneNode* camera = cameraOld;//Game->Device->getSceneManager()->addCameraSceneNode();
//Left eye...
vector3df pos;
matrix4 move;
move.setTranslation( vector3df(-1.5f,0.0f,0.0f) );
pos=(startMatrix*move).getTranslation();
driver->getOverrideMaterial().Material.ColorMask = ECP_RED;
driver->getOverrideMaterial().EnableFlags = EMF_COLOR_MASK;
driver->getOverrideMaterial().EnablePasses =
ESNRP_SKY_BOX|ESNRP_SOLID|ESNRP_TRANSPARENT|
ESNRP_TRANSPARENT_EFFECT|ESNRP_SHADOW;
camera->setPosition(pos);
camera->setTarget(focusPoint);
Game->Device->getSceneManager()->drawAll();
driver->clearBuffers(video::ECBF_DEPTH, video::SColor(255, 0, 0, 0));
//Right eye...
move.setTranslation( vector3df(1.5f,0.0f,0.0f) );
pos=(startMatrix*move).getTranslation();
driver->getOverrideMaterial().Material.ColorMask = ECP_GREEN + ECP_BLUE;
driver->getOverrideMaterial().EnableFlags = EMF_COLOR_MASK;
driver->getOverrideMaterial().EnablePasses =
ESNRP_SKY_BOX|ESNRP_SOLID|ESNRP_TRANSPARENT|
ESNRP_TRANSPARENT_EFFECT|ESNRP_SHADOW;
camera->setPosition(pos);
camera->setTarget(focusPoint);
Game->Device->getSceneManager()->drawAll();
driver->getOverrideMaterial().Material.ColorMask=ECP_ALL;
driver->getOverrideMaterial().EnableFlags=0;
driver->getOverrideMaterial().EnablePasses=0;
if (camera != cameraOld)
{
Game->Device->getSceneManager()->setActiveCamera(cameraOld);
camera->remove();
}
else
{
camera->setPosition(oldPosition);
camera->setTarget(oldTarget);
}
}
else
{
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, SColor(0,0,0,0));
Game->Device->getSceneManager()->drawAll();
}
Game->Device->getGUIEnvironment()->drawAll();
driver->endScene();
}

update the generic scene node

void CQuake3EventHandler::Animate()
{
u32 now = Game->Device->getTimer()->getTime();
Q3Player * player = Player + 0;
checkTimeFire ( player->Anim, 4, now );
// Query Scene Manager attributes
if ( player->Anim[0].flags & FIRED )
{
wchar_t msg[128];
IVideoDriver * driver = Game->Device->getVideoDriver();
#ifdef _IRR_SCENEMANAGER_DEBUG
IAttributes * attr = Game->Device->getSceneManager()->getParameters();
swprintf_irr ( msg, 128,
L"Q3 %s [%ls], FPS:%03d Tri:%.03fm Cull %d/%d nodes (%d,%d,%d)",
Game->CurrentMapName.c_str(),
driver->getName(),
driver->getFPS (),
(f32) driver->getPrimitiveCountDrawn( 0 ) * ( 1.f / 1000000.f ),
attr->getAttributeAsInt ( "culled" ),
attr->getAttributeAsInt ( "calls" ),
attr->getAttributeAsInt ( "drawn_solid" ),
attr->getAttributeAsInt ( "drawn_transparent" ),
attr->getAttributeAsInt ( "drawn_transparent_effect" )
);
#else
swprintf_irr ( msg, 128,
L"Q3 %s [%ls], FPS:%03d Tri:%.03fm",
Game->CurrentMapName.c_str(),
driver->getName(),
driver->getFPS (),
(f32) driver->getPrimitiveCountDrawn( 0 ) * ( 1.f / 1000000.f )
);
#endif
Game->Device->setWindowCaption( msg );
swprintf_irr ( msg, 128,
L"%03d fps, F1 GUI on/off, F2 respawn, F3-F6 toggle Nodes, F7 Collision on/off"
L", F8 Gravity on/off, Right Mouse Toggle GUI",
Game->Device->getVideoDriver()->getFPS ()
);
if ( gui.StatusLine )
gui.StatusLine->setText ( msg );
player->Anim[0].flags &= ~FIRED;
}
// idle..
if ( player->Anim[1].flags & FIRED )
{
if ( strcmp ( player->animation, "idle" ) )
player->setAnim ( "idle" );
player->Anim[1].flags &= ~FIRED;
}
createParticleImpacts ( now );
}

The main game states

void runGame ( GameData *game )
{
if ( game->retVal >= 3 )
return;
game->Device = (*game->createExDevice) ( game->deviceParam );
if ( 0 == game->Device)
{
// could not create selected driver.
game->retVal = 0;
return;
}
// create an event receiver based on current game data
CQuake3EventHandler *eventHandler = new CQuake3EventHandler( game );
// load stored config
game->load ( "explorer.cfg" );
// add our media directory and archive to the file system
for ( u32 i = 0; i < game->CurrentArchiveList.size(); ++i )
{
eventHandler->AddArchive ( game->CurrentArchiveList[i] );
}
// Load a Map or startup to the GUI
if ( game->CurrentMapName.size () )
{
eventHandler->LoadMap ( game->CurrentMapName, 1 );
if ( 0 == game->loadParam.loadSkyShader )
eventHandler->AddSky ( 1, "skydome2" );
eventHandler->CreatePlayers ();
eventHandler->CreateGUI ();
eventHandler->SetGUIActive ( 0 );
// set player to last position on restart
if ( game->retVal == 2 )
{
eventHandler->GetPlayer( 0 )->setpos ( game->PlayerPosition, game->PlayerRotation );
}
}
else
{
// start up empty
eventHandler->AddSky ( 1, "skydome2" );
eventHandler->CreatePlayers ();
eventHandler->CreateGUI ();
eventHandler->SetGUIActive ( 1 );
background_music ( "IrrlichtTheme.ogg" );
}
game->retVal = 3;
while( game->Device->run() )
{
eventHandler->Animate ();
eventHandler->Render ();
//if ( !game->Device->isWindowActive() )
game->Device->yield();
}
game->Device->setGammaRamp ( 1.f, 1.f, 1.f, 0.f, 0.f );
delete eventHandler;
}
#if defined (_IRR_WINDOWS_) && 0
#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
#endif

The main routine, doing all setup

int IRRCALLCONV main(int argc, char* argv[])
{
path prgname(argv[0]);
GameData game ( deletePathFromPath ( prgname, 1 ) );
// dynamically load irrlicht
const c8 * dllName = argc > 1 ? argv[1] : "irrlicht.dll";
game.createExDevice = load_createDeviceEx ( dllName );
if ( 0 == game.createExDevice )
{
game.retVal = 3;
printf ( "Could not load %s.\n", dllName );
return game.retVal; // could not load dll
}
// start without asking for driver
game.retVal = 1;
do
{
// if driver could not created, ask for another driver
if ( game.retVal == 0 )
{
game.setDefault ();
// ask user for driver
game.deviceParam.DriverType=driverChoiceConsole();
if (game.deviceParam.DriverType==video::EDT_COUNT)
game.retVal = 3;
}
runGame ( &game );
} while ( game.retVal < 3 );
return game.retVal;
}
+ + +

 

+ + diff --git a/examples/22.MaterialViewer/tutorial.html b/examples/22.MaterialViewer/tutorial.html new file mode 100644 index 00000000..20bc5d38 --- /dev/null +++ b/examples/22.MaterialViewer/tutorial.html @@ -0,0 +1,206 @@ + + + + + + + +Tutorial 22: Material Viewer + + + + +
+ + + +
+
+
+
Tutorial 22: Material Viewer
+
+
+
+022shot.jpg +
+

This example can be used to play around with material settings and watch the results. Only the default non-shader materials are used in here.

+

You have a node with a mesh, one dynamic light and global ambient light to play around with. You can move the light with cursor-keys and +/-. You can move the camera while left-mouse button is clicked.

// TODO: Should be possible to set all material values by the GUI.
// For now just change the defaultMaterial in CApp::init for the rest.
// TODO: Allow users to switch between a sphere and a box mesh.
#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
#include "main.h"
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

Variables within the empty namespace are globals which are restricted to this file.

namespace
{
// For the gui id's
enum EGUI_IDS
{
GUI_ID_OPEN_TEXTURE = 1,
GUI_ID_QUIT,
GUI_ID_MAX
};
// Name used in texture selection to clear the textures on the node
const core::stringw CLEAR_TEXTURE = L"CLEAR texture";
// some useful color constants
const video::SColor SCOL_BLACK = video::SColor(255, 0, 0, 0);
const video::SColor SCOL_BLUE = video::SColor(255, 0, 0, 255);
const video::SColor SCOL_CYAN = video::SColor(255, 0, 255, 255);
const video::SColor SCOL_GRAY = video::SColor(255, 128,128, 128);
const video::SColor SCOL_GREEN = video::SColor(255, 0, 255, 0);
const video::SColor SCOL_MAGENTA = video::SColor(255, 255, 0, 255);
const video::SColor SCOL_RED = video::SColor(255, 255, 0, 0);
const video::SColor SCOL_YELLOW = video::SColor(255, 255, 255, 0);
const video::SColor SCOL_WHITE = video::SColor(255, 255, 255, 255);
}; // namespace

Returns a new unique number on each call.

s32 makeUniqueId()
{
static int unique = GUI_ID_MAX;
++unique;
return unique;
}

Find out which vertex-type is needed for the given material type.

video::E_VERTEX_TYPE getVertexTypeForMaterialType(video::E_MATERIAL_TYPE materialType)
{
using namespace video;
switch ( materialType )
{
case EMT_SOLID:
return EVT_STANDARD;
case EMT_SOLID_2_LAYER:
return EVT_STANDARD;
case EMT_LIGHTMAP:
case EMT_LIGHTMAP_ADD:
case EMT_LIGHTMAP_M2:
case EMT_LIGHTMAP_M4:
case EMT_LIGHTMAP_LIGHTING:
case EMT_LIGHTMAP_LIGHTING_M2:
case EMT_LIGHTMAP_LIGHTING_M4:
return EVT_2TCOORDS;
case EMT_DETAIL_MAP:
return EVT_2TCOORDS;
case EMT_SPHERE_MAP:
return EVT_STANDARD;
case EMT_REFLECTION_2_LAYER:
return EVT_2TCOORDS;
case EMT_TRANSPARENT_ADD_COLOR:
return EVT_STANDARD;
case EMT_TRANSPARENT_ALPHA_CHANNEL:
return EVT_STANDARD;
case EMT_TRANSPARENT_ALPHA_CHANNEL_REF:
return EVT_STANDARD;
case EMT_TRANSPARENT_VERTEX_ALPHA:
return EVT_STANDARD;
case EMT_TRANSPARENT_REFLECTION_2_LAYER:
return EVT_2TCOORDS;
case EMT_NORMAL_MAP_SOLID:
case EMT_NORMAL_MAP_TRANSPARENT_ADD_COLOR:
case EMT_NORMAL_MAP_TRANSPARENT_VERTEX_ALPHA:
case EMT_PARALLAX_MAP_SOLID:
case EMT_PARALLAX_MAP_TRANSPARENT_ADD_COLOR:
case EMT_PARALLAX_MAP_TRANSPARENT_VERTEX_ALPHA:
return EVT_TANGENTS;
case EMT_ONETEXTURE_BLEND:
return EVT_STANDARD;
case EMT_FORCE_32BIT:
return EVT_STANDARD;
}
return EVT_STANDARD;
}

Custom GUI-control to edit colorvalues.

// Constructor
CColorControl::CColorControl(gui::IGUIEnvironment* guiEnv, const core::position2d<s32> & pos, const wchar_t *text, IGUIElement* parent, s32 id)
: gui::IGUIElement(gui::EGUIET_ELEMENT, guiEnv, parent,id, core::rect< s32 >(pos, pos+core::dimension2d<s32>(80, 75)))
, DirtyFlag(true)
, Color(0)
, ColorStatic(0)
, EditAlpha(0)
, EditRed(0)
, EditGreen(0)
, EditBlue(0)
{
using namespace gui;
ButtonSetId = makeUniqueId();
const core::rect< s32 > rectControls(0,0,AbsoluteRect.getWidth(),AbsoluteRect.getHeight() );
IGUIStaticText * groupElement = guiEnv->addStaticText (L"", rectControls, true, false, this, -1, false);
groupElement->setNotClipped(true);
guiEnv->addStaticText (text, core::rect<s32>(0,0,80,15), false, false, groupElement, -1, false);
EditAlpha = addEditForNumbers(guiEnv, core::position2d<s32>(0,15), L"a", -1, groupElement );
EditRed = addEditForNumbers(guiEnv, core::position2d<s32>(0,30), L"r", -1, groupElement );
EditGreen = addEditForNumbers(guiEnv, core::position2d<s32>(0,45), L"g", -1, groupElement );
EditBlue = addEditForNumbers(guiEnv, core::position2d<s32>(0,60), L"b", -1, groupElement );
ColorStatic = guiEnv->addStaticText (L"", core::rect<s32>(60,15,80,75), true, false, groupElement, -1, true);
guiEnv->addButton (core::rect<s32>(60,35,80,50), groupElement, ButtonSetId, L"set");
setEditsFromColor(Color);
}
// event receiver
bool CColorControl::OnEvent(const SEvent &event)
{
if ( event.EventType != EET_GUI_EVENT )
return false;
if ( event.GUIEvent.Caller->getID() == ButtonSetId && event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED )
{
Color = getColorFromEdits();
setEditsFromColor(Color);
}
return false;
}
// set the color values
void CColorControl::setColor(const video::SColor& col)
{
DirtyFlag = true;
Color = col;
setEditsFromColor(Color);
}
// Add a staticbox for a description + an editbox so users can enter numbers
gui::IGUIEditBox* CColorControl::addEditForNumbers(gui::IGUIEnvironment* guiEnv, const core::position2d<s32> & pos, const wchar_t *text, s32 id, gui::IGUIElement * parent)
{
using namespace gui;
core::rect< s32 > rect(pos, pos+core::dimension2d<s32>(10, 15));
guiEnv->addStaticText (text, rect, false, false, parent, -1, false);
rect += core::position2d<s32>( 20, 0 );
rect.LowerRightCorner.X += 20;
gui::IGUIEditBox* edit = guiEnv->addEditBox(L"0", rect, true, parent, id);
return edit;
}
// Get the color value from the editfields
video::SColor CColorControl::getColorFromEdits() const
{
video::SColor col;
if (EditAlpha)
{
u32 alpha = core::strtoul10(core::stringc(EditAlpha->getText()).c_str());
if (alpha > 255)
alpha = 255;
col.setAlpha(alpha);
}
if (EditRed)
{
u32 red = core::strtoul10(core::stringc(EditRed->getText()).c_str());
if (red > 255)
red = 255;
col.setRed(red);
}
if (EditGreen)
{
u32 green = core::strtoul10(core::stringc(EditGreen->getText()).c_str());
if (green > 255)
green = 255;
col.setGreen(green);
}
if (EditBlue)
{
u32 blue = core::strtoul10(core::stringc(EditBlue->getText()).c_str());
if (blue > 255)
blue = 255;
col.setBlue(blue);
}
return col;
}
// Fill the editfields with the value for the given color
void CColorControl::setEditsFromColor(video::SColor col)
{
DirtyFlag = true;
if ( EditAlpha )
EditAlpha->setText( core::stringw(col.getAlpha()).c_str() );
if ( EditRed )
EditRed->setText( core::stringw(col.getRed()).c_str() );
if ( EditGreen )
EditGreen->setText( core::stringw(col.getGreen()).c_str() );
if ( EditBlue )
EditBlue->setText( core::stringw(col.getBlue()).c_str() );
if ( ColorStatic )
ColorStatic->setBackgroundColor(col);
}

Custom GUI-control for to edit all colors typically used in materials and lights

// Constructor
CTypicalColorsControl::CTypicalColorsControl(gui::IGUIEnvironment* guiEnv, const core::position2d<s32> & pos, bool hasEmissive, IGUIElement* parent, s32 id)
: gui::IGUIElement(gui::EGUIET_ELEMENT, guiEnv, parent,id, core::rect<s32>(pos,pos+core::dimension2d<s32>(60,250)))
, ControlAmbientColor(0), ControlDiffuseColor(0), ControlSpecularColor(0), ControlEmissiveColor(0)
{
ControlAmbientColor = new CColorControl( guiEnv, core::position2d<s32>(0, 0), L"Ambient", this);
ControlDiffuseColor = new CColorControl( guiEnv, core::position2d<s32>(0, 75), L"Diffuse", this );
ControlSpecularColor = new CColorControl( guiEnv, core::position2d<s32>(0, 150), L"Specular", this );
if ( hasEmissive )
{
ControlEmissiveColor = new CColorControl( guiEnv, core::position2d<s32>(0, 225), L"Emissive", this );
}
}
// Destructor
CTypicalColorsControl::~CTypicalColorsControl()
{
ControlAmbientColor->drop();
ControlDiffuseColor->drop();
if ( ControlEmissiveColor )
ControlEmissiveColor->drop();
ControlSpecularColor->drop();
}
// Set the color values to those within the material
void CTypicalColorsControl::setColorsToMaterialColors(const video::SMaterial & material)
{
ControlAmbientColor->setColor(material.AmbientColor);
ControlDiffuseColor->setColor(material.DiffuseColor);
ControlEmissiveColor->setColor(material.EmissiveColor);
ControlSpecularColor->setColor(material.SpecularColor);
}
// Update all changed colors in the material
void CTypicalColorsControl::updateMaterialColors(video::SMaterial & material) const
{
if ( ControlAmbientColor->isDirty() )
material.AmbientColor = ControlAmbientColor->getColor();
if ( ControlDiffuseColor->isDirty() )
material.DiffuseColor = ControlDiffuseColor->getColor();
if ( ControlEmissiveColor->isDirty() )
material.EmissiveColor = ControlEmissiveColor->getColor();
if ( ControlSpecularColor->isDirty() )
material.SpecularColor = ControlSpecularColor->getColor();
}
// Set the color values to those from the light data
void CTypicalColorsControl::setColorsToLightDataColors(const video::SLight & lightData)
{
ControlAmbientColor->setColor(lightData.AmbientColor.toSColor());
ControlDiffuseColor->setColor(lightData.DiffuseColor.toSColor());
ControlSpecularColor->setColor(lightData.SpecularColor.toSColor());
}
// Update all changed colors in the light data
void CTypicalColorsControl::updateLightColors(video::SLight & lightData) const
{
if ( ControlAmbientColor->isDirty() )
lightData.AmbientColor = video::SColorf( ControlAmbientColor->getColor() );
if ( ControlDiffuseColor->isDirty() )
lightData.DiffuseColor = video::SColorf( ControlDiffuseColor->getColor() );
if ( ControlSpecularColor->isDirty() )
lightData.SpecularColor = video::SColorf(ControlSpecularColor->getColor() );
}
// To reset the dirty flags
void CTypicalColorsControl::resetDirty()
{
ControlAmbientColor->resetDirty();
ControlDiffuseColor->resetDirty();
ControlSpecularColor->resetDirty();
if ( ControlEmissiveColor )
ControlEmissiveColor->resetDirty();
}

GUI-Control to offer a selection of available textures.

CTextureControl::CTextureControl(gui::IGUIEnvironment* guiEnv, video::IVideoDriver * driver, const core::position2d<s32> & pos, IGUIElement* parent, s32 id)
: gui::IGUIElement(gui::EGUIET_ELEMENT, guiEnv, parent,id, core::rect<s32>(pos,pos+core::dimension2d<s32>(150,15)))
, DirtyFlag(true), ComboTexture(0)
{
core::rect<s32> rectCombo(0, 0, AbsoluteRect.getWidth(),AbsoluteRect.getHeight());
ComboTexture = guiEnv->addComboBox (rectCombo, this);
updateTextures(driver);
}
bool CTextureControl::OnEvent(const SEvent &event)
{
if ( event.EventType != EET_GUI_EVENT )
return false;
if ( event.GUIEvent.Caller == ComboTexture && event.GUIEvent.EventType == gui::EGET_COMBO_BOX_CHANGED )
{
DirtyFlag = true;
}
return false;
}
// Workaround for a problem with comboboxes.
// We have to get in front when the combobox wants to get in front or combobox-list might be drawn below other elements.
bool CTextureControl::bringToFront(IGUIElement* element)
{
bool result = gui::IGUIElement::bringToFront(element);
if ( Parent && element == ComboTexture )
result &= Parent->bringToFront(this);
return result;
}
// return selected texturename (if any, otherwise 0)
const wchar_t * CTextureControl::getSelectedTextureName() const
{
s32 selected = ComboTexture->getSelected();
if ( selected < 0 )
return 0;
return ComboTexture->getItem(selected);
}
void CTextureControl::selectTextureByName(const irr::core::stringw& name)
{
for (u32 i=0; i< ComboTexture->getItemCount(); ++i)
{
if ( name == ComboTexture->getItem(i))
{
ComboTexture->setSelected(i);
DirtyFlag = true;
return;
}
}
}
// Put the names of all currently loaded textures in a combobox
void CTextureControl::updateTextures(video::IVideoDriver * driver)
{
s32 oldSelected = ComboTexture->getSelected();
s32 selectNew = -1;
core::stringw oldTextureName;
if ( oldSelected >= 0 )
{
oldTextureName = ComboTexture->getItem(oldSelected);
}
ComboTexture->clear();
for ( u32 i=0; i < driver->getTextureCount(); ++i )
{
video::ITexture * texture = driver->getTextureByIndex(i);
core::stringw name( texture->getName() );
ComboTexture->addItem( name.c_str() );
if ( !oldTextureName.empty() && selectNew < 0 && name == oldTextureName )
selectNew = i;
}
// add another name which can be used to clear the texture
ComboTexture->addItem( CLEAR_TEXTURE.c_str() );
if ( CLEAR_TEXTURE == oldTextureName )
selectNew = ComboTexture->getItemCount()-1;
if ( selectNew >= 0 )
ComboTexture->setSelected(selectNew);
DirtyFlag = true;
}

Control which allows setting some of the material values for a meshscenenode

void CMaterialControl::init(scene::IMeshSceneNode* node, IrrlichtDevice * device, const core::position2d<s32> & pos, const wchar_t * description)
{
if ( Initialized || !node || !device) // initializing twice or with invalid data not allowed
return;
Driver = device->getVideoDriver ();
gui::IGUIEnvironment* guiEnv = device->getGUIEnvironment();
//scene::ISceneManager* smgr = device->getSceneManager();
const video::SMaterial & material = node->getMaterial(0);
s32 top = pos.Y;
// Description
guiEnv->addStaticText(description, core::rect<s32>(pos.X, top, pos.X+60, top+15), false, false, 0, -1, false);
top += 15;
// Control for material type
core::rect<s32> rectCombo(pos.X, top, 150, top+15);
top += 15;
ComboMaterial = guiEnv->addComboBox (rectCombo);
for ( int i=0; i <= (int)video::EMT_ONETEXTURE_BLEND; ++i )
{
ComboMaterial->addItem( core::stringw(video::sBuiltInMaterialTypeNames[i]).c_str() );
}
ComboMaterial->setSelected( (s32)material.MaterialType );
// Control to enable/disabling material lighting
core::rect<s32> rectBtn(core::position2d<s32>(pos.X, top), core::dimension2d<s32>(100, 15));
top += 15;
ButtonLighting = guiEnv->addButton (rectBtn, 0, -1, L"Lighting");
ButtonLighting->setIsPushButton(true);
ButtonLighting->setPressed(material.Lighting);
core::rect<s32> rectInfo( rectBtn.LowerRightCorner.X, rectBtn.UpperLeftCorner.Y, rectBtn.LowerRightCorner.X+40, rectBtn.UpperLeftCorner.Y+15 );
InfoLighting = guiEnv->addStaticText(L"", rectInfo, true, false );
InfoLighting->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER );
// Controls for colors
TypicalColorsControl = new CTypicalColorsControl(guiEnv, core::position2d<s32>(pos.X, top), true, guiEnv->getRootGUIElement());
top += 300;
TypicalColorsControl->setColorsToMaterialColors(material);
// Controls for selecting the material textures
guiEnv->addStaticText(L"Textures", core::rect<s32>(pos.X, top, pos.X+60, top+15), false, false, 0, -1, false);
top += 15;
for (irr::u32 i=0; i<irr::video::MATERIAL_MAX_TEXTURES; ++i)
{
TextureControls[i] = new CTextureControl(guiEnv, Driver, core::position2di(pos.X, top), guiEnv->getRootGUIElement());
top += 15;
}
Initialized = true;
}
void CMaterialControl::update(scene::IMeshSceneNode* sceneNode, scene::IMeshSceneNode* sceneNode2T, scene::IMeshSceneNode* sceneNodeTangents)
{
if ( !Initialized )
return;
video::SMaterial & material = sceneNode->getMaterial(0);
video::SMaterial & material2T = sceneNode2T->getMaterial(0);
video::SMaterial & materialTangents = sceneNodeTangents->getMaterial(0);
s32 selectedMaterial = ComboMaterial->getSelected();
if ( selectedMaterial >= (s32)video::EMT_SOLID && selectedMaterial <= (s32)video::EMT_ONETEXTURE_BLEND)
{
// Show the node which has a mesh to work with the currently selected material
video::E_VERTEX_TYPE vertexType = getVertexTypeForMaterialType((video::E_MATERIAL_TYPE)selectedMaterial);
switch ( vertexType )
{
case video::EVT_STANDARD:
material.MaterialType = (video::E_MATERIAL_TYPE)selectedMaterial;
sceneNode->setVisible(true);
sceneNode2T->setVisible(false);
sceneNodeTangents->setVisible(false);
break;
case video::EVT_2TCOORDS:
material2T.MaterialType = (video::E_MATERIAL_TYPE)selectedMaterial;
sceneNode->setVisible(false);
sceneNode2T->setVisible(true);
sceneNodeTangents->setVisible(false);
break;
case video::EVT_TANGENTS:
materialTangents.MaterialType = (video::E_MATERIAL_TYPE)selectedMaterial;
sceneNode->setVisible(false);
sceneNode2T->setVisible(false);
sceneNodeTangents->setVisible(true);
break;
}
}
// Always update materials of all nodes, otherwise the tool is confusing to use.
updateMaterial(material);
updateMaterial(material2T);
updateMaterial(materialTangents);
if ( ButtonLighting->isPressed() )
InfoLighting->setText(L"is on");
else
InfoLighting->setText(L"is off");
TypicalColorsControl->resetDirty();
for (irr::u32 i=0; i<irr::video::MATERIAL_MAX_TEXTURES; ++i)
TextureControls[i]->resetDirty();
}
void CMaterialControl::updateTextures()
{
for (irr::u32 i=0; i<irr::video::MATERIAL_MAX_TEXTURES; ++i)
TextureControls[i]->updateTextures(Driver);
}
void CMaterialControl::selectTextures(const irr::core::stringw& name)
{
for (irr::u32 i=0; i<irr::video::MATERIAL_MAX_TEXTURES; ++i)
TextureControls[i]->selectTextureByName(name);
}
bool CMaterialControl::isLightingEnabled() const
{
return ButtonLighting && ButtonLighting->isPressed();
}
void CMaterialControl::updateMaterial(video::SMaterial & material)
{
TypicalColorsControl->updateMaterialColors(material);
material.Lighting = ButtonLighting->isPressed();
for (irr::u32 i=0; i<irr::video::MATERIAL_MAX_TEXTURES; ++i)
{
if ( TextureControls[i]->isDirty() )
{
material.TextureLayer[i].Texture = Driver->getTexture( io::path(TextureControls[i]->getSelectedTextureName()) );
}
}
}

Control to allow setting the color values of a lightscenenode.

void CLightNodeControl::init(scene::ILightSceneNode* node, gui::IGUIEnvironment* guiEnv, const core::position2d<s32> & pos, const wchar_t * description)
{
if ( Initialized || !node || !guiEnv) // initializing twice or with invalid data not allowed
return;
guiEnv->addStaticText(description, core::rect<s32>(pos.X, pos.Y, pos.X+70, pos.Y+15), false, false, 0, -1, false);
TypicalColorsControl = new CTypicalColorsControl(guiEnv, core::position2d<s32>(pos.X, pos.Y+15), false, guiEnv->getRootGUIElement());
const video::SLight & lightData = node->getLightData();
TypicalColorsControl->setColorsToLightDataColors(lightData);
Initialized = true;
}
void CLightNodeControl::update(scene::ILightSceneNode* node)
{
if ( !Initialized )
return;
video::SLight & lightData = node->getLightData();
TypicalColorsControl->updateLightColors(lightData);
}

Main application class

Event handler

bool CApp::OnEvent(const SEvent &event)
{
if (event.EventType == EET_GUI_EVENT)
{
gui::IGUIEnvironment* env = Device->getGUIEnvironment();
switch(event.GUIEvent.EventType)
{
case gui::EGET_MENU_ITEM_SELECTED:
{
gui::IGUIContextMenu* menu = (gui::IGUIContextMenu*)event.GUIEvent.Caller;
s32 id = menu->getItemCommandId(menu->getSelectedItem());
switch(id)
{
case GUI_ID_OPEN_TEXTURE: // File -> Open Texture
env->addFileOpenDialog(L"Please select a texture file to open");
break;
case GUI_ID_QUIT: // File -> Quit
setRunning(false);
break;
}
}
break;
case gui::EGET_FILE_SELECTED:
{
// load the model file, selected in the file open dialog
gui::IGUIFileOpenDialog* dialog =
(gui::IGUIFileOpenDialog*)event.GUIEvent.Caller;
loadTexture(io::path(dialog->getFileName()).c_str());
}
break;
default:
break;
}
}
else if (event.EventType == EET_KEY_INPUT_EVENT)
{
KeysPressed[event.KeyInput.Key] = event.KeyInput.PressedDown;
}
else if (event.EventType == EET_MOUSE_INPUT_EVENT)
{
if (!MousePressed && event.MouseInput.isLeftPressed())
{
gui::IGUIEnvironment* guiEnv = Device->getGUIEnvironment();
if ( guiEnv->getHovered() == guiEnv->getRootGUIElement() ) // Click on background
{
MousePressed = true;
MouseStart.X = event.MouseInput.X;
MouseStart.Y = event.MouseInput.Y;
}
}
else if (MousePressed && !event.MouseInput.isLeftPressed())
{
MousePressed = false;
}
}
return false;
}
// Application initialization
// returns true when it was successful initialized, otherwise false.
bool CApp::init(int argc, char *argv[])
{
// ask user for driver
Config.DriverType=driverChoiceConsole();
if (Config.DriverType==video::EDT_COUNT)
return false;
// create the device with the settings from our config
Device = createDevice(Config.DriverType, Config.ScreenSize);
if (!Device)
return false;
Device->setWindowCaption( core::stringw(video::DRIVER_TYPE_NAMES[Config.DriverType]).c_str() );
Device->setEventReceiver(this);
scene::ISceneManager* smgr = Device->getSceneManager();
video::IVideoDriver * driver = Device->getVideoDriver ();
gui::IGUIEnvironment* guiEnv = Device->getGUIEnvironment();
MeshManipulator = smgr->getMeshManipulator();
// set a nicer font
gui::IGUISkin* skin = guiEnv->getSkin();
gui::IGUIFont* font = guiEnv->getFont(getExampleMediaPath() + "fonthaettenschweiler.bmp");
if (font)
skin->setFont(font);
// remove some alpha value because it makes those menus harder to read otherwise
video::SColor col3dHighLight( skin->getColor(gui::EGDC_APP_WORKSPACE) );
col3dHighLight.setAlpha(255);
video::SColor colHighLight( col3dHighLight );
skin->setColor(gui::EGDC_HIGH_LIGHT, colHighLight );
skin->setColor(gui::EGDC_3D_HIGH_LIGHT, col3dHighLight );
// Add some textures which are useful to test material settings
createDefaultTextures(driver);
// create a menu
gui::IGUIContextMenu * menuBar = guiEnv->addMenu();
menuBar->addItem(L"File", -1, true, true);
gui::IGUIContextMenu* subMenuFile = menuBar->getSubMenu(0);
subMenuFile->addItem(L"Open texture ...", GUI_ID_OPEN_TEXTURE);
subMenuFile->addSeparator();
subMenuFile->addItem(L"Quit", GUI_ID_QUIT);
// a static camera
Camera = smgr->addCameraSceneNode (0, core::vector3df(0, 40, -40),
core::vector3df(0, 10, 0),
-1);
// default material
video::SMaterial defaultMaterial;
defaultMaterial.Shininess = 20.f;
// add the nodes which are used to show the materials
SceneNode = smgr->addCubeSceneNode (30.0f, 0, -1,
core::vector3df(0, 0, 0),
core::vector3df(0.f, 45.f, 0.f),
core::vector3df(1.0f, 1.0f, 1.0f));
SceneNode->getMaterial(0) = defaultMaterial;
const s32 controlsTop = 20;
MeshMaterialControl = new CMaterialControl();
MeshMaterialControl->init( SceneNode, Device, core::position2d<s32>(10,controlsTop), L"Material" );
MeshMaterialControl->selectTextures(core::stringw("CARO_A8R8G8B8")); // set a useful default texture
// create nodes with other vertex types
scene::IMesh * mesh2T = MeshManipulator->createMeshWith2TCoords(SceneNode->getMesh());
SceneNode2T = smgr->addMeshSceneNode(mesh2T, 0, -1, SceneNode->getPosition(), SceneNode->getRotation(), SceneNode->getScale() );
mesh2T->drop();
scene::IMesh * meshTangents = MeshManipulator->createMeshWithTangents(SceneNode->getMesh(), false, false, false);
SceneNodeTangents = smgr->addMeshSceneNode(meshTangents, 0, -1
, SceneNode->getPosition(), SceneNode->getRotation(), SceneNode->getScale() );
meshTangents->drop();
// add one light
NodeLight = smgr->addLightSceneNode(0, core::vector3df(0, 0, -40),
video::SColorf(1.0f, 1.0f, 1.0f),
35.0f);
LightControl = new CLightNodeControl();
LightControl->init(NodeLight, guiEnv, core::position2d<s32>(550,controlsTop), L"Dynamic light" );
// one large cube around everything. That's mainly to make the light more obvious.
scene::IMeshSceneNode* backgroundCube = smgr->addCubeSceneNode (200.0f, 0, -1, core::vector3df(0, 0, 0),
core::vector3df(45, 0, 0),
core::vector3df(1.0f, 1.0f, 1.0f));
backgroundCube->getMaterial(0).BackfaceCulling = false; // we are within the cube, so we have to disable backface culling to see it
backgroundCube->getMaterial(0).EmissiveColor.set(255,50,50,50); // we keep some self lighting to keep texts visible
// Add a the mesh vertex color control
guiEnv->addStaticText(L"Mesh", core::rect<s32>(200, controlsTop, 270, controlsTop+15), false, false, 0, -1, false);
ControlVertexColors = new CColorControl( guiEnv, core::position2d<s32>(200, controlsTop+15), L"Vertex colors", guiEnv->getRootGUIElement());
video::S3DVertex * vertices = (video::S3DVertex *)SceneNode->getMesh()->getMeshBuffer(0)->getVertices();
if ( vertices )
{
ControlVertexColors->setColor(vertices[0].Color);
}
// Add a control for ambient light
GlobalAmbient = new CColorControl( guiEnv, core::position2d<s32>(550, 300), L"Global ambient", guiEnv->getRootGUIElement());
GlobalAmbient->setColor( smgr->getAmbientLight().toSColor() );
return true;
}

Update one frame

bool CApp::update()
{
using namespace irr;
video::IVideoDriver* videoDriver = Device->getVideoDriver();
if ( !Device->run() )
return false;
// Figure out delta time since last frame
ITimer * timer = Device->getTimer();
u32 newTick = timer->getRealTime();
f32 deltaTime = RealTimeTick > 0 ? f32(newTick-RealTimeTick)/1000.f : 0.f; // in seconds
RealTimeTick = newTick;
if ( Device->isWindowActive() || Config.RenderInBackground )
{
gui::IGUIEnvironment* guiEnv = Device->getGUIEnvironment();
scene::ISceneManager* smgr = Device->getSceneManager();
gui::IGUISkin * skin = guiEnv->getSkin();
// update our controls
MeshMaterialControl->update(SceneNode, SceneNode2T, SceneNodeTangents);
LightControl->update(NodeLight);
// Update vertices
if ( ControlVertexColors->isDirty() )
{
MeshManipulator->setVertexColors (SceneNode->getMesh(), ControlVertexColors->getColor());
MeshManipulator->setVertexColors (SceneNode2T->getMesh(), ControlVertexColors->getColor());
MeshManipulator->setVertexColors (SceneNodeTangents->getMesh(), ControlVertexColors->getColor());
ControlVertexColors->resetDirty();
}
// update ambient light settings
if ( GlobalAmbient->isDirty() )
{
smgr->setAmbientLight( GlobalAmbient->getColor() );
GlobalAmbient->resetDirty();
}
// Let the user move the light around
const float zoomSpeed = 10.f * deltaTime;
const float rotationSpeed = 100.f * deltaTime;
if ( KeysPressed[KEY_PLUS] || KeysPressed[KEY_ADD])
ZoomOut(NodeLight, zoomSpeed);
if ( KeysPressed[KEY_MINUS] || KeysPressed[KEY_SUBTRACT])
ZoomOut(NodeLight, -zoomSpeed);
if ( KeysPressed[KEY_RIGHT])
RotateHorizontal(NodeLight, rotationSpeed);
if ( KeysPressed[KEY_LEFT])
RotateHorizontal(NodeLight, -rotationSpeed);
UpdateRotationAxis(NodeLight, LightRotationAxis);
if ( KeysPressed[KEY_UP])
RotateAroundAxis(NodeLight, rotationSpeed, LightRotationAxis);
if ( KeysPressed[KEY_DOWN])
RotateAroundAxis(NodeLight, -rotationSpeed, LightRotationAxis);
// Let the user move the camera around
if (MousePressed)
{
gui::ICursorControl* cursorControl = Device->getCursorControl();
const core::position2d<s32>& mousePos = cursorControl->getPosition ();
RotateHorizontal(Camera, rotationSpeed * (MouseStart.X - mousePos.X));
RotateAroundAxis(Camera, rotationSpeed * (mousePos.Y - MouseStart.Y), CameraRotationAxis);
MouseStart = mousePos;
}
// draw everything
video::SColor bkColor( skin->getColor(gui::EGDC_APP_WORKSPACE) );
videoDriver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, bkColor);
smgr->drawAll();
guiEnv->drawAll();
if ( MeshMaterialControl->isLightingEnabled() )
{
// draw a line from the light to the target
video::SMaterial lineMaterial;
lineMaterial.Lighting = false;
videoDriver->setMaterial(lineMaterial);
videoDriver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
videoDriver->draw3DLine(NodeLight->getAbsolutePosition(), SceneNode->getAbsolutePosition());
}
videoDriver->endScene();
}
// be nice
Device->sleep( 5 );
return true;
}
// Close down the application
void CApp::quit()
{
IsRunning = false;
delete LightControl;
LightControl = NULL;
delete MeshMaterialControl;
MeshMaterialControl = NULL;
if ( ControlVertexColors )
{
ControlVertexColors->drop();
ControlVertexColors = NULL;
}
if ( GlobalAmbient )
{
GlobalAmbient->drop();
GlobalAmbient = NULL;
}
if ( Device )
{
Device->closeDevice();
Device->drop();
Device = NULL;
}
}
// Create some useful textures.
void CApp::createDefaultTextures(video::IVideoDriver * driver)
{
const u32 width = 256;
const u32 height = 256;
video::IImage * imageA8R8G8B8 = driver->createImage (video::ECF_A8R8G8B8, core::dimension2d<u32>(width, height));
if ( !imageA8R8G8B8 )
return;
const u32 pitch = imageA8R8G8B8->getPitch();
// Some nice square-pattern with 9 typical colors
// Note that the function put readability over speed, you shouldn't use setPixel at runtime but for initialization it's nice.
for ( u32 y = 0; y < height; ++ y )
{
for ( u32 x = 0; x < pitch; ++x )
{
if ( y < height/3 )
{
if ( x < width/3 )
imageA8R8G8B8->setPixel (x, y, SCOL_BLACK);
else if ( x < 2*width/3 )
imageA8R8G8B8->setPixel (x, y, SCOL_BLUE);
else
imageA8R8G8B8->setPixel (x, y, SCOL_CYAN);
}
else if ( y < 2*height/3 )
{
if ( x < width/3 )
imageA8R8G8B8->setPixel (x, y, SCOL_GRAY);
else if ( x < 2*width/3 )
imageA8R8G8B8->setPixel (x, y, SCOL_GREEN);
else
imageA8R8G8B8->setPixel (x, y, SCOL_MAGENTA);
}
else
{
if ( x < width/3 )
imageA8R8G8B8->setPixel (x, y, SCOL_RED);
else if ( x < 2*width/3 )
imageA8R8G8B8->setPixel (x, y, SCOL_YELLOW);
else
imageA8R8G8B8->setPixel (x, y, SCOL_WHITE);
}
}
}
driver->addTexture (io::path("CARO_A8R8G8B8"), imageA8R8G8B8);
// all white
imageA8R8G8B8->fill(SCOL_WHITE);
driver->addTexture (io::path("WHITE_A8R8G8B8"), imageA8R8G8B8);
// all black
imageA8R8G8B8->fill(SCOL_BLACK);
driver->addTexture (io::path("BLACK_A8R8G8B8"), imageA8R8G8B8);
// gray-scale
for ( u32 y = 0; y < height; ++ y )
{
for ( u32 x = 0; x < pitch; ++x )
{
imageA8R8G8B8->setPixel (x, y, video::SColor(y, x,x,x) );
}
}
driver->addTexture (io::path("GRAYSCALE_A8R8G8B8"), imageA8R8G8B8);
imageA8R8G8B8->drop();
}
// Load a texture and make sure nodes know it when more textures are available.
void CApp::loadTexture(const io::path &name)
{
Device->getVideoDriver()->getTexture(name);
MeshMaterialControl->updateTextures();
}
void CApp::RotateHorizontal(irr::scene::ISceneNode* node, irr::f32 angle)
{
if ( node )
{
core::vector3df pos(node->getPosition());
core::vector2df dir(pos.X, pos.Z);
dir.rotateBy(angle);
pos.X = dir.X;
pos.Z = dir.Y;
node->setPosition(pos);
}
}
void CApp::RotateAroundAxis(irr::scene::ISceneNode* node, irr::f32 angle, const irr::core::vector3df& axis)
{
if ( node )
{
// TOOD: yeah, doesn't rotate around top/bottom yet. Fixes welcome.
core::vector3df pos(node->getPosition());
core::matrix4 mat;
mat.setRotationAxisRadians (core::degToRad(angle), axis);
mat.rotateVect(pos);
node->setPosition(pos);
}
}
void CApp::ZoomOut(irr::scene::ISceneNode* node, irr::f32 units)
{
if ( node )
{
core::vector3df pos(node->getPosition());
irr::f32 len = pos.getLength() + units;
pos.setLength(len);
node->setPosition(pos);
}
}
void CApp::UpdateRotationAxis(irr::scene::ISceneNode* node, irr::core::vector3df& axis)
{
// Find a perpendicular axis to the x,z vector. If none found (vector straight up/down) continue to use the existing one.
core::vector3df pos(node->getPosition());
if ( !core::equals(pos.X, 0.f) || !core::equals(pos.Z, 0.f) )
{
axis.X = -pos.Z;
axis.Z = pos.X;
axis.normalize();
}
}

Short main as most is done in classes.

int main(int argc, char *argv[])
{
CApp APP;
if ( !APP.init(argc, argv) )
{
printf("init failed\n");
APP.quit();
return 1;
}
APP.setRunning(true);

main application loop

while(APP.isRunning())
{
if ( !APP.update() )
break;
}
APP.quit();
return 0;
}
+ + +

 

+ + diff --git a/examples/23.SMeshHandling/tutorial.html b/examples/23.SMeshHandling/tutorial.html new file mode 100644 index 00000000..ebb196ee --- /dev/null +++ b/examples/23.SMeshHandling/tutorial.html @@ -0,0 +1,209 @@ + + + + + + + +Tutorial 23: SMeshBufferHandling + + + + +
+ + + +
+
+
+
Tutorial 23: SMeshBufferHandling
+
+
+
+023shot.jpg +
+

A tutorial by geoff.

+

In this tutorial we'll learn how to create custom meshes and deal with them with Irrlicht. We'll create an interesting heightmap with some lighting effects. With keys 1,2,3 you can choose a different mesh layout, which is put into the mesh buffers as desired. All positions, normals, etc. are updated accordingly.

+

Ok, let's start with the headers (I think there's nothing to say about it)

#include <irrlicht.h>
#include "driverChoice.h"
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
//Namespaces for the engine
using namespace irr;
using namespace video;
using namespace core;
using namespace scene;
using namespace io;
using namespace gui;

This is the type of the functions which work out the colour.

typedef SColor colour_func(f32 x, f32 y, f32 z);

Here comes a set of functions which can be used for coloring the nodes while creating the mesh.

// Greyscale, based on the height.
SColor grey(f32, f32, f32 z)
{
u32 n = (u32)(255.f * z);
return SColor(255, n, n, n);
}
// Interpolation between blue and white, with red added in one
// direction and green in the other.
SColor yellow(f32 x, f32 y, f32)
{
return SColor(255, 128 + (u32)(127.f * x), 128 + (u32)(127.f * y), 255);
}
// Pure white.
SColor white(f32, f32, f32) { return SColor(255, 255, 255, 255); }

The type of the functions which generate the heightmap. x and y range between -0.5 and 0.5, and s is the scale of the heightmap.

typedef f32 generate_func(s16 x, s16 y, f32 s);
// An interesting sample function :-)
f32 eggbox(s16 x, s16 y, f32 s)
{
const f32 r = 4.f*sqrtf((f32)(x*x + y*y))/s;
const f32 z = (f32)exp(-r * 2) * (cosf(0.2f * x) + cosf(0.2f * y));
return 0.25f+0.25f*z;
}
// A rather dumb sine function :-/
f32 moresine(s16 x, s16 y, f32 s)
{
const f32 xx=0.3f*(f32)x/s;
const f32 yy=12*y/s;
const f32 z = sinf(xx*xx+yy)*sinf(xx+yy*yy);
return 0.25f + 0.25f * z;
}
// A simple function
f32 justexp(s16 x, s16 y, f32 s)
{
const f32 xx=6*x/s;
const f32 yy=6*y/s;
const f32 z = (xx*xx+yy*yy);
return 0.3f*z*cosf(xx*yy);
}

A simple class for representing heightmaps. Most of this should be obvious.

class HeightMap
{
private:
const u16 Width;
const u16 Height;
f32 s;
core::array<f32> data;
public:
HeightMap(u16 _w, u16 _h) : Width(_w), Height(_h), s(0.f), data(0)
{
s = sqrtf((f32)(Width * Width + Height * Height));
data.set_used(Width * Height);
}
// Fill the heightmap with values generated from f.
void generate(generate_func f)
{
u32 i=0;
for(u16 y = 0; y < Height; ++y)
for(u16 x = 0; x < Width; ++x)
set(i++, calc(f, x, y));
}
u16 height() const { return Height; }
u16 width() const { return Width; }
f32 calc(generate_func f, u16 x, u16 y) const
{
const f32 xx = (f32)x - Width*0.5f;
const f32 yy = (f32)y - Height*0.5f;
return f((u16)xx, (u16)yy, s);
}
// The height at (x, y) is at position y * Width + x.
void set(u16 x, u16 y, f32 z) { data[y * Width + x] = z; }
void set(u32 i, f32 z) { data[i] = z; }
f32 get(u16 x, u16 y) const { return data[y * Width + x]; }

The only difficult part. This considers the normal at (x, y) to be the cross product of the vectors between the adjacent points in the horizontal and vertical directions.

+

s is a scaling factor, which is necessary if the height units are different from the coordinate units; for example, if your map has heights in meters and the coordinates are in units of a kilometer.

vector3df getnormal(u16 x, u16 y, f32 s) const
{
const f32 zc = get(x, y);
f32 zl, zr, zu, zd;
if (x == 0)
{
zr = get(x + 1, y);
zl = zc + zc - zr;
}
else if (x == Width - 1)
{
zl = get(x - 1, y);
zr = zc + zc - zl;
}
else
{
zr = get(x + 1, y);
zl = get(x - 1, y);
}
if (y == 0)
{
zd = get(x, y + 1);
zu = zc + zc - zd;
}
else if (y == Height - 1)
{
zu = get(x, y - 1);
zd = zc + zc - zu;
}
else
{
zd = get(x, y + 1);
zu = get(x, y - 1);
}
return vector3df(s * 2 * (zl - zr), 4, s * 2 * (zd - zu)).normalize();
}
};

A class which generates a mesh from a heightmap.

class TMesh
{
private:
u16 Width;
u16 Height;
f32 Scale;
public:
SMesh* Mesh;
TMesh() : Width(0), Height(0), Scale(1.f), Mesh(0)
{
Mesh = new SMesh();
}
~TMesh()
{
Mesh->drop();
}
// Unless the heightmap is small, it won't all fit into a single
// SMeshBuffer. This function chops it into pieces and generates a
// buffer from each one.
void init(const HeightMap &hm, f32 scale, colour_func cf, IVideoDriver *driver)
{
Scale = scale;
const u32 mp = driver -> getMaximalPrimitiveCount();
Width = hm.width();
Height = hm.height();
const u32 sw = mp / (6 * Height); // the width of each piece
u32 i=0;
for(u32 y0 = 0; y0 < Height; y0 += sw)
{
u16 y1 = y0 + sw;
if (y1 >= Height)
y1 = Height - 1; // the last one might be narrower
addstrip(hm, cf, y0, y1, i);
++i;
}
if (i<Mesh->getMeshBufferCount())
{
// clear the rest
for (u32 j=i; j<Mesh->getMeshBufferCount(); ++j)
{
Mesh->getMeshBuffer(j)->drop();
}
Mesh->MeshBuffers.erase(i,Mesh->getMeshBufferCount()-i);
}
// set dirty flag to make sure that hardware copies of this
// buffer are also updated, see IMesh::setHardwareMappingHint
Mesh->setDirty();
Mesh->recalculateBoundingBox();
}
// Generate a SMeshBuffer which represents all the vertices and
// indices for values of y between y0 and y1, and add it to the
// mesh.
void addstrip(const HeightMap &hm, colour_func cf, u16 y0, u16 y1, u32 bufNum)
{
SMeshBuffer *buf = 0;
if (bufNum<Mesh->getMeshBufferCount())
{
buf = (SMeshBuffer*)Mesh->getMeshBuffer(bufNum);
}
else
{
// create new buffer
buf = new SMeshBuffer();
Mesh->addMeshBuffer(buf);
// to simplify things we drop here but continue using buf
buf->drop();
}
buf->Vertices.set_used((1 + y1 - y0) * Width);
u32 i=0;
for (u16 y = y0; y <= y1; ++y)
{
for (u16 x = 0; x < Width; ++x)
{
const f32 z = hm.get(x, y);
const f32 xx = (f32)x/(f32)Width;
const f32 yy = (f32)y/(f32)Height;
S3DVertex& v = buf->Vertices[i++];
v.Pos.set(x, Scale * z, y);
v.Normal.set(hm.getnormal(x, y, Scale));
v.Color=cf(xx, yy, z);
v.TCoords.set(xx, yy);
}
}
buf->Indices.set_used(6 * (Width - 1) * (y1 - y0));
i=0;
for(u16 y = y0; y < y1; ++y)
{
for(u16 x = 0; x < Width - 1; ++x)
{
const u16 n = (y-y0) * Width + x;
buf->Indices[i]=n;
buf->Indices[++i]=n + Width;
buf->Indices[++i]=n + Width + 1;
buf->Indices[++i]=n + Width + 1;
buf->Indices[++i]=n + 1;
buf->Indices[++i]=n;
++i;
}
}
buf->recalculateBoundingBox();
}
};

Our event receiver implementation, taken from tutorial 4.

class MyEventReceiver : public IEventReceiver
{
public:
// This is the one method that we have to implement
virtual bool OnEvent(const SEvent& event)
{
// Remember whether each key is down or up
if (event.EventType == irr::EET_KEY_INPUT_EVENT)
KeyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
return false;
}
// This is used to check whether a key is being held down
virtual bool IsKeyDown(EKEY_CODE keyCode) const
{
return KeyIsDown[keyCode];
}
MyEventReceiver()
{
for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
KeyIsDown[i] = false;
}
private:
// We use this array to store the current state of each key
bool KeyIsDown[KEY_KEY_CODES_COUNT];
};

Much of this is code taken from some of the examples. We merely set up a mesh from a heightmap, light it with a moving light, and allow the user to navigate around it.

int main(int argc, char* argv[])
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
MyEventReceiver receiver;
IrrlichtDevice* device = createDevice(driverType,
core::dimension2du(800, 600), 32, false, false, false,
&receiver);
if(device == 0)
return 1;
IVideoDriver *driver = device->getVideoDriver();
ISceneManager *smgr = device->getSceneManager();
device->setWindowCaption(L"Irrlicht Example for SMesh usage.");

Create the custom mesh and initialize with a heightmap

TMesh mesh;
HeightMap hm = HeightMap(255, 255);
hm.generate(eggbox);
mesh.init(hm, 50.f, grey, driver);
// Add the mesh to the scene graph
IMeshSceneNode* meshnode = smgr -> addMeshSceneNode(mesh.Mesh);
meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING, false);
// light is just for nice effects
ILightSceneNode *node = smgr->addLightSceneNode(0, vector3df(0,100,0),
SColorf(1.0f, 0.6f, 0.7f, 1.0f), 500.0f);
if (node)
{
node->getLightData().Attenuation.set(0.f, 1.f/500.f, 0.f);
ISceneNodeAnimator* anim = smgr->createFlyCircleAnimator(vector3df(0,150,0),250.0f);
if (anim)
{
node->addAnimator(anim);
anim->drop();
}
}
ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();
if (camera)
{
camera->setPosition(vector3df(-20.f, 150.f, -20.f));
camera->setTarget(vector3df(200.f, -80.f, 150.f));
camera->setFarValue(20000.0f);
}

Just a usual render loop with event handling. The custom mesh is a usual part of the scene graph which gets rendered by drawAll.

while(device->run())
{
if(!device->isWindowActive())
{
device->sleep(100);
continue;
}
if(receiver.IsKeyDown(irr::KEY_KEY_W))
{
meshnode->setMaterialFlag(video::EMF_WIREFRAME, !meshnode->getMaterial(0).Wireframe);
}
else if(receiver.IsKeyDown(irr::KEY_KEY_1))
{
hm.generate(eggbox);
mesh.init(hm, 50.f, grey, driver);
}
else if(receiver.IsKeyDown(irr::KEY_KEY_2))
{
hm.generate(moresine);
mesh.init(hm, 50.f, yellow, driver);
}
else if(receiver.IsKeyDown(irr::KEY_KEY_3))
{
hm.generate(justexp);
mesh.init(hm, 50.f, yellow, driver);
}
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, SColor(0xff000000));
smgr->drawAll();
driver->endScene();
}
device->drop();
return 0;
}

That's it! Just compile and play around with the program.

+
+ + +

 

+ + diff --git a/examples/24.CursorControl/tutorial.html b/examples/24.CursorControl/tutorial.html new file mode 100644 index 00000000..c374ea19 --- /dev/null +++ b/examples/24.CursorControl/tutorial.html @@ -0,0 +1,205 @@ + + + + + + + +Tutorial 24: CursorControl + + + + +
+ + + +
+
+
+
Tutorial 24: CursorControl
+
+
+
+024shot.jpg +
+

Show how to modify cursors and offer some useful tool-functions for creating cursors. It can also be used for experiments with the mouse in general.

#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
const int DELAY_TIME = 3000;
enum ETimerAction
{
ETA_MOUSE_VISIBLE,
ETA_MOUSE_INVISIBLE,
};

Structure to allow delayed execution of some actions.

struct TimerAction
{
u32 TargetTime;
ETimerAction Action;
};
struct SAppContext
{
SAppContext()
: Device(0), InfoStatic(0), EventBox(0), CursorBox(0), SpriteBox(0)
, ButtonSetVisible(0), ButtonSetInvisible(0), ButtonSimulateBadFps(0)
, ButtonChangeIcon(0)
, SimulateBadFps(false)
{
}
void update()
{
if (!Device)
return;
u32 timeNow = Device->getTimer()->getTime();
for ( u32 i=0; i < TimerActions.size(); ++i )
{
if ( timeNow >= TimerActions[i].TargetTime )
{
runTimerAction(TimerActions[i]);
TimerActions.erase(i);
}
else
{
++i;
}
}
}
void runTimerAction(const TimerAction& action)
{
if (ETA_MOUSE_VISIBLE == action.Action)
{
Device->getCursorControl()->setVisible(true);
ButtonSetVisible->setEnabled(true);
}
else if ( ETA_MOUSE_INVISIBLE == action.Action)
{
Device->getCursorControl()->setVisible(false);
ButtonSetInvisible->setEnabled(true);
}
}

Add another icon which the user can click and select as cursor later on.

void addIcon(const stringw& name, const SCursorSprite &sprite, bool addCursor=true)
{
// Sprites are just icons - not yet cursors. They can be displayed by Irrlicht sprite functions and be used to create cursors.
SpriteBox->addItem(name.c_str(), sprite.SpriteId);
Sprites.push_back(sprite);
// create the cursor together with the icon?
if ( addCursor )
{

Here we create a hardware cursor from a sprite

Device->getCursorControl()->addIcon(sprite);
// ... and add it to the cursors selection listbox to the other system cursors.
CursorBox->addItem(name.c_str());
}
}
IrrlichtDevice * Device;
gui::IGUIStaticText * InfoStatic;
gui::IGUIListBox * EventBox;
gui::IGUIListBox * CursorBox;
gui::IGUIListBox * SpriteBox;
gui::IGUIButton * ButtonSetVisible;
gui::IGUIButton * ButtonSetInvisible;
gui::IGUIButton * ButtonSimulateBadFps;
gui::IGUIButton * ButtonChangeIcon;
array<TimerAction> TimerActions;
bool SimulateBadFps;
array<SCursorSprite> Sprites;
};

Helper function to print mouse event names into a stringw

void PrintMouseEventName(const SEvent& event, stringw &result)
{
switch ( event.MouseInput.Event )
{
case EMIE_LMOUSE_PRESSED_DOWN: result += stringw(L"EMIE_LMOUSE_PRESSED_DOWN"); break;
case EMIE_RMOUSE_PRESSED_DOWN: result += stringw(L"EMIE_RMOUSE_PRESSED_DOWN"); break;
case EMIE_MMOUSE_PRESSED_DOWN: result += stringw(L"EMIE_MMOUSE_PRESSED_DOWN"); break;
case EMIE_LMOUSE_LEFT_UP: result += stringw(L"EMIE_LMOUSE_LEFT_UP"); break;
case EMIE_RMOUSE_LEFT_UP: result += stringw(L"EMIE_RMOUSE_LEFT_UP"); break;
case EMIE_MMOUSE_LEFT_UP: result += stringw(L"EMIE_MMOUSE_LEFT_UP"); break;
case EMIE_MOUSE_MOVED: result += stringw(L"EMIE_MOUSE_MOVED"); break;
case EMIE_MOUSE_WHEEL: result += stringw(L"EMIE_MOUSE_WHEEL"); break;
case EMIE_LMOUSE_DOUBLE_CLICK: result += stringw(L"EMIE_LMOUSE_DOUBLE_CLICK"); break;
case EMIE_RMOUSE_DOUBLE_CLICK: result += stringw(L"EMIE_RMOUSE_DOUBLE_CLICK"); break;
case EMIE_MMOUSE_DOUBLE_CLICK: result += stringw(L"EMIE_MMOUSE_DOUBLE_CLICK"); break;
case EMIE_LMOUSE_TRIPLE_CLICK: result += stringw(L"EMIE_LMOUSE_TRIPLE_CLICK"); break;
case EMIE_RMOUSE_TRIPLE_CLICK: result += stringw(L"EMIE_RMOUSE_TRIPLE_CLICK"); break;
case EMIE_MMOUSE_TRIPLE_CLICK: result += stringw(L"EMIE_MMOUSE_TRIPLE_CLICK"); break;
default:
break;
}
}

Helper function to print all the state information which get from a mouse-event into a stringw

void PrintMouseState(const SEvent& event, stringw &result)
{
result += stringw(L"X: ");
result += stringw(event.MouseInput.X);
result += stringw(L"\n");
result += stringw(L"Y: ");
result += stringw(event.MouseInput.Y);
result += stringw(L"\n");
result += stringw(L"Wheel: ");
result += stringw(event.MouseInput.Wheel);
result += stringw(L"\n");
result += stringw(L"Shift: ");
if ( event.MouseInput.Shift )
result += stringw(L"true\n");
else
result += stringw(L"false\n");
result += stringw(L"Control: ");
if ( event.MouseInput.Control )
result += stringw(L"true\n");
else
result += stringw(L"false\n");
result += stringw(L"ButtonStates: ");
result += stringw(event.MouseInput.ButtonStates);
result += stringw(L"\n");
result += stringw(L"isLeftPressed: ");
if ( event.MouseInput.isLeftPressed() )
result += stringw(L"true\n");
else
result += stringw(L"false\n");
result += stringw(L"isRightPressed: ");
if ( event.MouseInput.isRightPressed() )
result += stringw(L"true\n");
else
result += stringw(L"false\n");
result += stringw(L"isMiddlePressed: ");
if ( event.MouseInput.isMiddlePressed() )
result += stringw(L"true\n");
else
result += stringw(L"false\n");
result += stringw(L"Event: ");
PrintMouseEventName(event, result);
result += stringw(L"\n");
}

A typical event receiver.

class MyEventReceiver : public IEventReceiver
{
public:
MyEventReceiver(SAppContext & context) : Context(context) { }
virtual bool OnEvent(const SEvent& event)
{
if (event.EventType == EET_GUI_EVENT )
{
switch ( event.GUIEvent.EventType )
{
case EGET_BUTTON_CLICKED:
{
u32 timeNow = Context.Device->getTimer()->getTime();
TimerAction action;
action.TargetTime = timeNow + DELAY_TIME;
if ( event.GUIEvent.Caller == Context.ButtonSetVisible )
{
action.Action = ETA_MOUSE_VISIBLE;
Context.TimerActions.push_back(action);
Context.ButtonSetVisible->setEnabled(false);
}
else if ( event.GUIEvent.Caller == Context.ButtonSetInvisible )
{
action.Action = ETA_MOUSE_INVISIBLE;
Context.TimerActions.push_back(action);
Context.ButtonSetInvisible->setEnabled(false);
}
else if ( event.GUIEvent.Caller == Context.ButtonSimulateBadFps )
{
Context.SimulateBadFps = Context.ButtonSimulateBadFps->isPressed();
}
else if ( event.GUIEvent.Caller == Context.ButtonChangeIcon )
{

Replace an existing cursor icon by another icon. The user has to select both - the icon which should be replaced and the icon which will replace it.

s32 selectedCursor = Context.CursorBox->getSelected();
s32 selectedSprite = Context.SpriteBox->getSelected();
if ( selectedCursor >= 0 && selectedSprite >= 0 )
{

This does replace the icon.

Context.Device->getCursorControl()->changeIcon((ECURSOR_ICON)selectedCursor, Context.Sprites[selectedSprite] );

Do also show the new icon.

Context.Device->getCursorControl()->setActiveIcon( ECURSOR_ICON(selectedCursor) );
}
}
}
break;
case EGET_LISTBOX_CHANGED:
case EGET_LISTBOX_SELECTED_AGAIN:
{
if ( event.GUIEvent.Caller == Context.CursorBox )
{

Find out which cursor the user selected

s32 selected = Context.CursorBox->getSelected();
if ( selected >= 0 )
{

Here we set the new cursor icon which will now be used within our window.

Context.Device->getCursorControl()->setActiveIcon( ECURSOR_ICON(selected) );
}
}
}
break;
default:
break;
}
}
if (event.EventType == EET_MOUSE_INPUT_EVENT)
{
stringw infoText;
PrintMouseState(event, infoText);
Context.InfoStatic->setText(infoText.c_str());
if ( event.MouseInput.Event != EMIE_MOUSE_MOVED && event.MouseInput.Event != EMIE_MOUSE_WHEEL ) // no spam
{
infoText = L"";
PrintMouseEventName(event, infoText);
Context.EventBox->insertItem(0, infoText.c_str(), -1);
}
}
return false;
}
private:
SAppContext & Context;
};

Use several imagefiles as animation frames for a sprite which can be used as cursor icon. The images in those files all need to have the same size. Return sprite index on success or -1 on failure

s32 AddAnimatedIconToSpriteBank( gui::IGUISpriteBank * spriteBank, video::IVideoDriver* driver, const array< io::path >& files, u32 frameTime )
{
if ( !spriteBank || !driver || !files.size() )
return -1;
video::ITexture * tex = driver->getTexture( files[0] );
if ( tex )
{
array< rect<s32> >& spritePositions = spriteBank->getPositions();
u32 idxRect = spritePositions.size();
spritePositions.push_back( rect<s32>(0,0, tex->getSize().Width, tex->getSize().Height) );
SGUISprite sprite;
sprite.frameTime = frameTime;
array< SGUISprite >& sprites = spriteBank->getSprites();
u32 startIdx = spriteBank->getTextureCount();
for ( u32 f=0; f < files.size(); ++f )
{
tex = driver->getTexture( files[f] );
if ( tex )
{
spriteBank->addTexture( driver->getTexture(files[f]) );
gui::SGUISpriteFrame frame;
frame.rectNumber = idxRect;
frame.textureNumber = startIdx+f;
sprite.Frames.push_back( frame );
}
}
sprites.push_back( sprite );
return sprites.size()-1;
}
return -1;
}

Use several images within one imagefile as animation frames for a sprite which can be used as cursor icon The sizes of the icons within that file all need to have the same size Return sprite index on success or -1 on failure

s32 AddAnimatedIconToSpriteBank( gui::IGUISpriteBank * spriteBank, video::IVideoDriver* driver, const io::path& file, const array< rect<s32> >& rects, u32 frameTime )
{
if ( !spriteBank || !driver || !rects.size() )
return -1;
video::ITexture * tex = driver->getTexture( file );
if ( tex )
{
array< rect<s32> >& spritePositions = spriteBank->getPositions();
u32 idxRect = spritePositions.size();
u32 idxTex = spriteBank->getTextureCount();
spriteBank->addTexture( tex );
SGUISprite sprite;
sprite.frameTime = frameTime;
array< SGUISprite >& sprites = spriteBank->getSprites();
for ( u32 i=0; i < rects.size(); ++i )
{
spritePositions.push_back( rects[i] );
gui::SGUISpriteFrame frame;
frame.rectNumber = idxRect+i;
frame.textureNumber = idxTex;
sprite.Frames.push_back( frame );
}
sprites.push_back( sprite );
return sprites.size()-1;
}
return -1;
}

Create a non-animated icon from the given file and position and put it into the spritebank. We can use this icon later on in a cursor.

s32 AddIconToSpriteBank( gui::IGUISpriteBank * spriteBank, video::IVideoDriver* driver, const io::path& file, const core::rect<s32>& rect )
{
if ( !spriteBank || !driver )
return -1;
video::ITexture * tex = driver->getTexture( file );
if ( tex )
{
core::array< core::rect<irr::s32> >& spritePositions = spriteBank->getPositions();
spritePositions.push_back( rect );
array< SGUISprite >& sprites = spriteBank->getSprites();
spriteBank->addTexture( tex );
gui::SGUISpriteFrame frame;
frame.rectNumber = spritePositions.size()-1;
frame.textureNumber = spriteBank->getTextureCount()-1;
SGUISprite sprite;
sprite.frameTime = 0;
sprite.Frames.push_back( frame );
sprites.push_back( sprite );
return sprites.size()-1;
}
return -1;
}
int main()
{
video::E_DRIVER_TYPE driverType = driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
IrrlichtDevice * device = createDevice(driverType, dimension2d<u32>(640, 480));
if (device == 0)
return 1; // could not create selected driver.
// It's sometimes of interest to know how the mouse behaves after a resize
device->setResizable(true);
device->setWindowCaption(L"Cursor control - Irrlicht engine tutorial");
video::IVideoDriver* driver = device->getVideoDriver();
IGUIEnvironment* env = device->getGUIEnvironment();
gui::IGUISpriteBank * SpriteBankIcons;
SAppContext context;
context.Device = device;
rect< s32 > rectInfoStatic(10,10, 200, 200);
env->addStaticText (L"Cursor state information", rectInfoStatic, true, true);
rectInfoStatic.UpperLeftCorner += dimension2di(0, 15);
context.InfoStatic = env->addStaticText (L"", rectInfoStatic, true, true);
rect< s32 > rectEventBox(10,210, 200, 400);
env->addStaticText (L"click events (new on top)", rectEventBox, true, true);
rectEventBox.UpperLeftCorner += dimension2di(0, 15);
context.EventBox = env->addListBox(rectEventBox);
rect< s32 > rectCursorBox(210,10, 400, 250);
env->addStaticText (L"cursors, click to set the active one", rectCursorBox, true, true);
rectCursorBox.UpperLeftCorner += dimension2di(0, 15);
context.CursorBox = env->addListBox(rectCursorBox);
rect< s32 > rectSpriteBox(210,260, 400, 400);
env->addStaticText (L"sprites", rectSpriteBox, true, true);
rectSpriteBox.UpperLeftCorner += dimension2di(0, 15);
context.SpriteBox = env->addListBox(rectSpriteBox);
context.ButtonSetVisible = env->addButton( rect<s32>( 410, 20, 560, 40 ), 0, -1, L"set visible (delayed)" );
context.ButtonSetInvisible = env->addButton( rect<s32>( 410, 50, 560, 70 ), 0, -1, L"set invisible (delayed)" );
context.ButtonSimulateBadFps = env->addButton( rect<s32>( 410, 80, 560, 100 ), 0, -1, L"simulate bad FPS" );
context.ButtonSimulateBadFps->setIsPushButton(true);
context.ButtonChangeIcon = env->addButton( rect<s32>( 410, 140, 560, 160 ), 0, -1, L"replace cursor icon\n(cursor+sprite must be selected)" );
// set the names for all the system cursors
for ( int i=0; i < (int)gui::ECI_COUNT; ++i )
{
context.CursorBox->addItem(stringw( GUICursorIconNames[i] ).c_str());
}

Create sprites which then can be used as cursor icons.

SpriteBankIcons = env->addEmptySpriteBank(io::path("cursor_icons"));
context.SpriteBox->setSpriteBank(SpriteBankIcons);
const io::path mediaPath = getExampleMediaPath();
// create one animated icon from several files
array< io::path > files;
files.push_back( io::path(mediaPath + "icon_crosshairs16x16bw1.png") );
files.push_back( io::path(mediaPath + "icon_crosshairs16x16bw2.png") );
files.push_back( io::path(mediaPath + "icon_crosshairs16x16bw3.png") );
files.push_back( io::path(mediaPath + "icon_crosshairs16x16bw3.png") );
files.push_back( io::path(mediaPath + "icon_crosshairs16x16bw2.png") );
SCursorSprite spriteBw; // the sprite + some additional information needed for cursors
spriteBw.SpriteId = AddAnimatedIconToSpriteBank( SpriteBankIcons, driver, files, 200 );
spriteBw.SpriteBank = SpriteBankIcons;
spriteBw.HotSpot = position2d<s32>(7,7);
context.addIcon(L"crosshair_bw", spriteBw);
// create one animated icon from one file
array< rect<s32> > iconRects;
iconRects.push_back( rect<s32>(0,0, 16, 16) );
iconRects.push_back( rect<s32>(16,0, 32, 16) );
iconRects.push_back( rect<s32>(0,16, 16, 32) );
iconRects.push_back( rect<s32>(0,16, 16, 32) );
iconRects.push_back( rect<s32>(16,0, 32, 16) );
SCursorSprite spriteCol; // the sprite + some additional information needed for cursors
spriteCol.SpriteId = AddAnimatedIconToSpriteBank( SpriteBankIcons, driver, io::path(mediaPath + "icon_crosshairs16x16col.png"), iconRects, 200 );
spriteCol.HotSpot = position2d<s32>(7,7);
spriteCol.SpriteBank = SpriteBankIcons;
context.addIcon(L"crosshair_colored", spriteCol);
// Create some non-animated icons
rect<s32> rectIcon;
SCursorSprite spriteNonAnimated(SpriteBankIcons, 0, position2d<s32>(7,7));
rectIcon = rect<s32>(0,0, 16, 16);
spriteNonAnimated.SpriteId = AddIconToSpriteBank( SpriteBankIcons, driver, io::path(mediaPath + "icon_crosshairs16x16col.png"), rectIcon );
context.addIcon(L"crosshair_col1", spriteNonAnimated, false);
rectIcon = rect<s32>(16,0, 32, 16);
spriteNonAnimated.SpriteId = AddIconToSpriteBank( SpriteBankIcons, driver, io::path(mediaPath + "icon_crosshairs16x16col.png"), rectIcon );
context.addIcon(L"crosshair_col2", spriteNonAnimated, false);
rectIcon = rect<s32>(0,16, 16, 32);
spriteNonAnimated.SpriteId = AddIconToSpriteBank( SpriteBankIcons, driver, io::path(mediaPath + "icon_crosshairs16x16col.png"), rectIcon );
context.addIcon(L"crosshair_col3", spriteNonAnimated, false);
MyEventReceiver receiver(context);
device->setEventReceiver(&receiver);
while(device->run() && driver)
{
// if (device->isWindowActive())
{
u32 realTimeNow = device->getTimer()->getRealTime();
context.update();
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, SColor(0,200,200,200));
env->drawAll();
// draw custom sprite with Irrlicht functions for comparison. It should usually look the same as the cursors.
if ( context.SpriteBox )
{
s32 selectedSprite = context.SpriteBox->getSelected();
if ( selectedSprite >= 0 && context.Sprites[selectedSprite].SpriteId >= 0 )
{
SpriteBankIcons->draw2DSprite(u32(context.Sprites[selectedSprite].SpriteId), position2di(580, 140), 0, video::SColor(255, 255, 255, 255), 0, realTimeNow);
}
}
driver->endScene();
}
// By simulating bad fps we can find out if hardware-support for cursors works or not. If it works the cursor will move as usual,while it otherwise will just update with 2 fps now.
if ( context.SimulateBadFps )
{
device->sleep(500); // 2 fps
}
else
{
device->sleep(10);
}
}
device->drop();
return 0;
}
+ + +

 

+ + diff --git a/examples/25.XmlHandling/main.cpp b/examples/25.XmlHandling/main.cpp index d022e5c2..c12afe2e 100644 --- a/examples/25.XmlHandling/main.cpp +++ b/examples/25.XmlHandling/main.cpp @@ -76,7 +76,8 @@ public: /* Load xml from disk, overwrite default settings The xml we are trying to load has the following structure - settings nested in sections nested in the root node, like so + settings nested in sections nested in the root node, like: + \verbatim
 		
 		
@@ -87,6 +88,7 @@ public:
 			
 		
 	
+ \endverbatim */ bool load() { diff --git a/examples/25.XmlHandling/tutorial.html b/examples/25.XmlHandling/tutorial.html new file mode 100644 index 00000000..7fd4c938 --- /dev/null +++ b/examples/25.XmlHandling/tutorial.html @@ -0,0 +1,219 @@ + + + + + + + +Tutorial 25: Xml Handling + + + + +
+ + + +
+
+
+
Tutorial 25: Xml Handling
+
+
+
+025shot.jpg +
+

Demonstrates loading and saving of configurations via XML

+
Author
Y.M. Bosman <yoran.nosp@m..bos.nosp@m.man@g.nosp@m.mail.nosp@m..com>
+

This demo features a fully usable system for configuration handling. The code can easily be integrated into own apps.

+
#include <irrlicht.h>
#include "exampleHelper.h"
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

SettingManager class.

+

This class loads and writes the settings and manages the options.

+

The class makes use of irrMap which is a an associative arrays using a red-black tree it allows easy mapping of a key to a value, along the way there is some information on how to use it.

class SettingManager
{
public:
// Construct setting managers and set default settings
SettingManager(const stringw& settings_file): SettingsFile(settings_file), NullDevice(0)
{
// Irrlicht null device, we want to load settings before we actually created our device, therefore, nulldevice
NullDevice = irr::createDevice(irr::video::EDT_NULL);
//DriverOptions is an irrlicht map,
//we can insert values in the map in two ways by calling insert(key,value) or by using the [key] operator
//the [] operator overrides values if they already exist
DriverOptions.insert(L"Software", EDT_SOFTWARE);
DriverOptions.insert(L"OpenGL", EDT_OPENGL);
DriverOptions.insert(L"Direct3D9", EDT_DIRECT3D9);
//some resolution options
ResolutionOptions.insert(L"640x480", dimension2du(640,480));
ResolutionOptions.insert(L"800x600", dimension2du(800,600));
ResolutionOptions.insert(L"1024x768", dimension2du(1024,768));
//our preferred defaults
SettingMap.insert(L"driver", L"Direct3D9");
SettingMap.insert(L"resolution", L"640x480");
SettingMap.insert(L"fullscreen", L"0"); //0 is false
}
// Destructor, you could store settings automatically on exit of your
// application if you wanted to in our case we simply drop the
// nulldevice
~SettingManager()
{
if (NullDevice)
{
NullDevice->closeDevice();
NullDevice->drop();
}
};

Load xml from disk, overwrite default settings The xml we are trying to load has the following structure settings nested in sections nested in the root node, like:

    <pre>
+        <?xml version="1.0"?>
+        <mygame>
+            <video>
+                <setting name="driver" value="Direct3D9" />
+                <setting name="fullscreen" value="0" />
+                <setting name="resolution" value="1024x768" />
+            </video>
+        </mygame>
+    </pre>
bool load()
{
//if not able to create device don't attempt to load
if (!NullDevice)
return false;
irr::io::IXMLReader* xml = NullDevice->getFileSystem()->createXMLReader(SettingsFile); //create xml reader
if (!xml)
return false;
const stringw settingTag(L"setting"); //we'll be looking for this tag in the xml
stringw currentSection; //keep track of our current section
const stringw videoTag(L"video"); //constant for videotag
//while there is more to read
while (xml->read())
{
//check the node type
switch (xml->getNodeType())
{
//we found a new element
case irr::io::EXN_ELEMENT:
{
//we currently are in the empty or mygame section and find the video tag so we set our current section to video
if (currentSection.empty() && videoTag.equals_ignore_case(xml->getNodeName()))
{
currentSection = videoTag;
}
//we are in the video section and we find a setting to parse
else if (currentSection.equals_ignore_case(videoTag) && settingTag.equals_ignore_case(xml->getNodeName() ))
{
//read in the key
stringw key = xml->getAttributeValueSafe(L"name");
//if there actually is a key to set
if (!key.empty())
{
//set the setting in the map to the value,
//the [] operator overrides values if they already exist or inserts a new key value
//pair into the settings map if it was not defined yet
SettingMap[key] = xml->getAttributeValueSafe(L"value");
}
}
//..
// You can add your own sections and tags to read in here
//..
}
break;
//we found the end of an element
case irr::io::EXN_ELEMENT_END:
//we were at the end of the video section so we reset our tag
currentSection=L"";
break;
default:
break;
}
}
// don't forget to delete the xml reader
xml->drop();
return true;
}
// Save the xml to disk. We use the nulldevice.
bool save()
{
//if not able to create device don't attempt to save
if (!NullDevice)
return false;
//create xml writer
irr::io::IXMLWriter* xwriter = NullDevice->getFileSystem()->createXMLWriter( SettingsFile );
if (!xwriter)
return false;
//write out the obligatory xml header. Each xml-file needs to have exactly one of those.
xwriter->writeXMLHeader();
//start element mygame, you replace the label "mygame" with anything you want
xwriter->writeElement(L"mygame");
xwriter->writeLineBreak(); //new line
//start section with video settings
xwriter->writeElement(L"video");
xwriter->writeLineBreak(); //new line
// getIterator gets us a pointer to the first node of the settings map
// every iteration we increase the iterator which gives us the next map node
// until we reach the end we write settings one by one by using the nodes key and value functions
map<stringw, stringw>::Iterator i = SettingMap.getIterator();
for(; !i.atEnd(); i++)
{
//write element as <setting name="key" value="x" />
//the second parameter indicates this is an empty element with no children, just attributes
xwriter->writeElement(L"setting",true, L"name", i->getKey().c_str(), L"value",i->getValue().c_str() );
xwriter->writeLineBreak();
}
xwriter->writeLineBreak();
//close video section
xwriter->writeClosingTag(L"video");
xwriter->writeLineBreak();
//..
// You can add writing sound settings, savegame information etc
//..
//close mygame section
xwriter->writeClosingTag(L"mygame");
//delete xml writer
xwriter->drop();
return true;
}
// Set setting in our manager
void setSetting(const stringw& name, const stringw& value)
{
SettingMap[name]=value;
}
// set setting overload to quickly assign integers to our setting map
void setSetting(const stringw& name, s32 value)
{
SettingMap[name]=stringw(value);
}
// Get setting as string
stringw getSetting(const stringw& key) const
{
//the find function or irrmap returns a pointer to a map Node
//if the key can be found, otherwise it returns null
//the map node has the function getValue and getKey, as we already know the key, we return node->getValue()
map<stringw, stringw>::Node* n = SettingMap.find(key);
if (n)
return n->getValue();
else
return L"";
}
//
bool getSettingAsBoolean(const stringw& key ) const
{
stringw s = getSetting(key);
if (s.empty())
return false;
return s.equals_ignore_case(L"1");
}
//
s32 getSettingAsInteger(const stringw& key) const
{
//we implicitly cast to string instead of stringw because strtol10 does not accept wide strings
const stringc s = getSetting(key);
if (s.empty())
return 0;
return strtol10(s.c_str());
}
public:
map<stringw, s32> DriverOptions; //available options for driver config
map<stringw, dimension2du> ResolutionOptions; //available options for resolution config
private:
SettingManager(const SettingManager& other); // defined but not implemented
SettingManager& operator=(const SettingManager& other); // defined but not implemented
map<stringw, stringw> SettingMap; //current config
stringw SettingsFile; // location of the xml, usually the
irr::IrrlichtDevice* NullDevice;
};

Application context for global variables

struct SAppContext
{
SAppContext()
: Device(0),Gui(0), Driver(0), Settings(0), ShouldQuit(false),
ButtonSave(0), ButtonExit(0), ListboxDriver(0),
ListboxResolution(0), CheckboxFullscreen(0)
{
}
~SAppContext()
{
if (Settings)
delete Settings;
if (Device)
{
Device->closeDevice();
Device->drop();
}
}
IrrlichtDevice* Device;
IGUIEnvironment* Gui;
IVideoDriver* Driver;
SettingManager* Settings;
bool ShouldQuit;
//settings dialog
IGUIButton* ButtonSave;
IGUIButton* ButtonExit;
IGUIListBox* ListboxDriver;
IGUIListBox* ListboxResolution;
IGUICheckBox* CheckboxFullscreen;
};

A typical event receiver.

class MyEventReceiver : public IEventReceiver
{
public:
MyEventReceiver(SAppContext & a) : App(a) { }
virtual bool OnEvent(const SEvent& event)
{
if (event.EventType == EET_GUI_EVENT )
{
switch ( event.GUIEvent.EventType )
{
//handle button click events
case EGET_BUTTON_CLICKED:
{
//Our save button was called so we obtain the settings from our dialog and save them
if ( event.GUIEvent.Caller == App.ButtonSave )
{
//if there is a selection write it
if ( App.ListboxDriver->getSelected() != -1)
App.Settings->setSetting(L"driver", App.ListboxDriver->getListItem(App.ListboxDriver->getSelected()));
//if there is a selection write it
if ( App.ListboxResolution->getSelected() != -1)
App.Settings->setSetting(L"resolution", App.ListboxResolution->getListItem(App.ListboxResolution->getSelected()));
App.Settings->setSetting(L"fullscreen", App.CheckboxFullscreen->isChecked());
if (App.Settings->save())
{
App.Gui->addMessageBox(L"settings save",L"settings saved, please restart for settings to change effect","",true);
}
}
// cancel/exit button clicked, tell the application to exit
else if ( event.GUIEvent.Caller == App.ButtonExit)
{
App.ShouldQuit = true;
}
}
break;
default:
break;
}
}
return false;
}
private:
SAppContext & App;
};

Function to create a video settings dialog This dialog shows the current settings from the configuration xml and allows them to be changed

void createSettingsDialog(SAppContext& app)
{
// first get rid of alpha in gui
for (irr::s32 i=0; i<irr::gui::EGDC_COUNT ; ++i)
{
irr::video::SColor col = app.Gui->getSkin()->getColor((irr::gui::EGUI_DEFAULT_COLOR)i);
col.setAlpha(255);
app.Gui->getSkin()->setColor((irr::gui::EGUI_DEFAULT_COLOR)i, col);
}
//create video settings windows
gui::IGUIWindow* windowSettings = app.Gui->addWindow(rect<s32>(10,10,400,400),true,L"Videosettings");
app.Gui->addStaticText (L"Select your desired video settings", rect< s32 >(10,20, 200, 40), false, true, windowSettings);
// add listbox for driver choice
app.Gui->addStaticText (L"Driver", rect< s32 >(10,50, 200, 60), false, true, windowSettings);
app.ListboxDriver = app.Gui->addListBox(rect<s32>(10,60,220,120), windowSettings, 1,true);
//add all available options to the driver choice listbox
map<stringw, s32>::Iterator i = app.Settings->DriverOptions.getIterator();
for(; !i.atEnd(); i++)
app.ListboxDriver->addItem(i->getKey().c_str());
//set currently selected driver
app.ListboxDriver->setSelected(app.Settings->getSetting("driver").c_str());
// add listbox for resolution choice
app.Gui->addStaticText (L"Resolution", rect< s32 >(10,130, 200, 140), false, true, windowSettings);
app.ListboxResolution = app.Gui->addListBox(rect<s32>(10,140,220,200), windowSettings, 1,true);
//add all available options to the resolution listbox
map<stringw, dimension2du>::Iterator ri = app.Settings->ResolutionOptions.getIterator();
for(; !ri.atEnd(); ri++)
app.ListboxResolution->addItem(ri->getKey().c_str());
//set currently selected resolution
app.ListboxResolution->setSelected(app.Settings->getSetting("resolution").c_str());
//add checkbox to toggle fullscreen, initially set to loaded setting
app.CheckboxFullscreen = app.Gui->addCheckBox(
app.Settings->getSettingAsBoolean("fullscreen"),
rect<s32>(10,220,220,240), windowSettings, -1,
L"Fullscreen");
//last but not least add save button
app.ButtonSave = app.Gui->addButton(
rect<s32>(80,250,150,270), windowSettings, 2,
L"Save video settings");
//exit/cancel button
app.ButtonExit = app.Gui->addButton(
rect<s32>(160,250,240,270), windowSettings, 2,
L"Cancel and exit");
}

The main function. Creates all objects and does the XML handling.

int main()
{
//create new application context
SAppContext app;
//create device creation parameters that can get overwritten by our settings file
SIrrlichtCreationParameters param;
param.DriverType = EDT_SOFTWARE;
param.WindowSize.set(640,480);
// Try to load config.
// I leave it as an exercise of the reader to store the configuration in the local application data folder,
// the only logical place to store config data for games. For all other operating systems I redirect to your manuals
app.Settings = new SettingManager(getExampleMediaPath() + "settings.xml");
if ( !app.Settings->load() )
{
// ...
// Here add your own exception handling, for now we continue because there are defaults set in SettingManager constructor
// ...
}
else
{
//settings xml loaded from disk,
//map driversetting to driver type and test if the setting is valid
//the DriverOptions map contains string representations mapped to to irrlicht E_DRIVER_TYPE enum
//e.g "direct3d9" will become 4
//see DriverOptions in the settingmanager class for details
map<stringw, s32>::Node* driver = app.Settings->DriverOptions.find( app.Settings->getSetting("driver") );
if (driver)
{
if ( irr::IrrlichtDevice::isDriverSupported( static_cast<E_DRIVER_TYPE>( driver->getValue() )))
{
// selected driver is supported, so we use it.
param.DriverType = static_cast<E_DRIVER_TYPE>( driver->getValue());
}
}
//map resolution setting to dimension in a similar way as demonstrated above
map<stringw, dimension2du>::Node* res = app.Settings->ResolutionOptions.find( app.Settings->getSetting("resolution") );
if (res)
{
param.WindowSize = res->getValue();
}
//get fullscreen setting from config
param.Fullscreen = app.Settings->getSettingAsBoolean("fullscreen");
}
//create the irrlicht device using the settings
app.Device = createDeviceEx(param);
if (app.Device == 0)
{
// You can add your own exception handling on driver failure
exit(0);
}
app.Device->setWindowCaption(L"Xmlhandling - Irrlicht engine tutorial");
app.Driver = app.Device->getVideoDriver();
app.Gui = app.Device->getGUIEnvironment();
createSettingsDialog(app);
//set event receiver so we can respond to gui events
MyEventReceiver receiver(app);
app.Device->setEventReceiver(&receiver);
//enter main loop
while (!app.ShouldQuit && app.Device->run())
{
if (app.Device->isWindowActive())
{
app.Driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, SColor(0,200,200,200));
app.Gui->drawAll();
app.Driver->endScene();
}
app.Device->sleep(10);
}
//app destroys device in destructor
return 0;
}
+ + +

 

+ + diff --git a/examples/26.OcclusionQuery/tutorial.html b/examples/26.OcclusionQuery/tutorial.html new file mode 100644 index 00000000..f9ec97c7 --- /dev/null +++ b/examples/26.OcclusionQuery/tutorial.html @@ -0,0 +1,207 @@ + + + + + + + +Tutorial 26: OcclusionQuery + + + + +
+ + + +
+
+
+
Tutorial 26: OcclusionQuery
+
+
+
+026shot.jpg +
+

This tutorial shows how to speed up rendering by use of the OcclusionQuery feature. The usual rendering tries to avoid rendering of scene nodes by culling those nodes which are outside the visible area, the view frustum. However, this technique does not cope with occluded objects which are still in the line of sight, but occluded by some larger object between the object and the eye (camera). Occlusion queries check exactly that. The queries basically measure the number of pixels that a previous render left on the screen. Since those pixels cannot be recognized at the end of a rendering anymore, the pixel count is measured directly when rendering. Thus, one needs to render the occluder (the object in front) first. This object needs to write to the z-buffer in order to become a real occluder. Then the node is rendered and in case a z-pass happens, i.e. the pixel is written to the framebuffer, the pixel is counted in the query. The result of a query is the number of pixels which got through. One can, based on this number, judge if the scene node is visible enough to be rendered, or if the node should be removed in the next round. Also note that the number of pixels is a safe over approximation in general. The pixels might be overdrawn later on, and the GPU tries to avoid inaccuracies which could lead to false negatives in the queries.

+

As you might have recognized already, we had to render the node to get the numbers. So where's the benefit, you might say. There are several ways where occlusion queries can help. It is often a good idea to just render the bbox of the node instead of the actual mesh. This is really fast and is a safe over approximation. If you need a more exact render with the actual geometry, it's a good idea to render with just basic solid material. Avoid complex shaders and state changes through textures. There's no need while just doing the occlusion query. At least if the render is not used for the actual scene. This is the third way to optimize occlusion queries. Just check the queries every 5th or 10th frame, or even less frequent. This depends on the movement speed of the objects and camera.

#ifdef _MSC_VER
// We'll also define this to stop MSVC complaining about sprintf().
#define _CRT_SECURE_NO_WARNINGS
#pragma comment(lib, "Irrlicht.lib")
#endif
#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
using namespace irr;

We need keyboard input events to switch some parameters

class MyEventReceiver : public IEventReceiver
{
public:
// This is the one method that we have to implement
virtual bool OnEvent(const SEvent& event)
{
// Remember whether each key is down or up
if (event.EventType == irr::EET_KEY_INPUT_EVENT)
KeyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
return false;
}
// This is used to check whether a key is being held down
virtual bool IsKeyDown(EKEY_CODE keyCode) const
{
return KeyIsDown[keyCode];
}
MyEventReceiver()
{
for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
KeyIsDown[i] = false;
}
private:
// We use this array to store the current state of each key
bool KeyIsDown[KEY_KEY_CODES_COUNT];
};

We create an irr::IrrlichtDevice and the scene nodes. One occluder, one occluded. The latter is a complex sphere, which has many triangles.

int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// create device
MyEventReceiver receiver;
IrrlichtDevice* device = createDevice(driverType,
core::dimension2d<u32>(640, 480), 16, false, false, false, &receiver);
if (device == 0)
return 1; // could not create selected driver.
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
const io::path mediaPath = getExampleMediaPath();
smgr->getGUIEnvironment()->addStaticText(L"Press Space to hide occluder.", core::recti(10,10, 200,50));

Create the node to be occluded. We create a sphere node with high poly count.

scene::ISceneNode * node = smgr->addSphereSceneNode(10, 64);
if (node)
{
node->setPosition(core::vector3df(0,0,60));
node->setMaterialTexture(0, driver->getTexture(mediaPath + "wall.bmp"));
node->setMaterialFlag(video::EMF_LIGHTING, false);
}

Now we create another node, the occluder. It's a simple plane.

scene::ISceneNode* plane = smgr->addMeshSceneNode(smgr->addHillPlaneMesh(
"plane", core::dimension2df(10,10), core::dimension2du(2,2)), 0, -1,
core::vector3df(0,0,20), core::vector3df(270,0,0));
if (plane)
{
plane->setMaterialTexture(0, driver->getTexture(mediaPath + "t351sml.jpg"));
plane->setMaterialFlag(video::EMF_LIGHTING, false);
plane->setMaterialFlag(video::EMF_BACK_FACE_CULLING, true);
}

Here we create the occlusion query. Because we don't have a plain mesh scene node (ESNT_MESH or ESNT_ANIMATED_MESH), we pass the base geometry as well. Instead, we could also pass a simpler mesh or the bounding box. But we will use a time based method, where the occlusion query renders to the frame buffer and in case of success (occlusion), the mesh is not drawn for several frames.

driver->addOcclusionQuery(node, ((scene::IMeshSceneNode*)node)->getMesh());

We have done everything, just a camera and draw it. We also write the current frames per second and the name of the driver to the caption of the window to examine the render speedup. We also store the time for measuring the time since the last occlusion query ran and store whether the node should be visible in the next frames.

smgr->addCameraSceneNode();
int lastFPS = -1;
u32 timeNow = device->getTimer()->getTime();
bool nodeVisible=true;
while(device->run())
{
plane->setVisible(!receiver.IsKeyDown(irr::KEY_SPACE));
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(255,113,113,133));

First, we draw the scene, possibly without the occluded element. This is necessary because we need the occluder to be drawn first. You can also use several scene managers to collect a number of possible occluders in a separately rendered scene.

node->setVisible(nodeVisible);
smgr->drawAll();
smgr->getGUIEnvironment()->drawAll();

Once in a while, here every 100 ms, we check the visibility. We run the queries, update the pixel value, and query the result. Since we already rendered the node we render the query invisible. The update is made blocking, as we need the result immediately. If you don't need the result immediately, e.g. because you have other things to render, you can call the update non-blocking. This gives the GPU more time to pass back the results without flushing the render pipeline. If the update was called non-blocking, the result from getOcclusionQueryResult is either the previous value, or 0xffffffff if no value has been generated at all, yet. The result is taken immediately as visibility flag for the node.

if (device->getTimer()->getTime()-timeNow>100)
{
driver->runAllOcclusionQueries(false);
driver->updateAllOcclusionQueries();
nodeVisible=driver->getOcclusionQueryResult(node)>0;
timeNow=device->getTimer()->getTime();
}
driver->endScene();
int fps = driver->getFPS();
if (lastFPS != fps)
{
core::stringw tmp(L"OcclusionQuery Example [");
tmp += driver->getName();
tmp += L"] fps: ";
tmp += fps;
device->setWindowCaption(tmp.c_str());
lastFPS = fps;
}
}

In the end, delete the Irrlicht device.

device->drop();
return 0;
}

That's it. Compile and play around with the program.

+
+ + +

 

+ + diff --git a/examples/27.PostProcessing/tutorial.html b/examples/27.PostProcessing/tutorial.html new file mode 100644 index 00000000..505c3841 --- /dev/null +++ b/examples/27.PostProcessing/tutorial.html @@ -0,0 +1,209 @@ + + + + + + + +Tutorial 27: Post Processing + + + + +
+ + + +
+
+
+
Tutorial 27: Post Processing
+
+
+
+027shot.jpg +
+

This tutorial shows how to implement post processing for D3D9 and OpenGL with the engine. In order to do post processing, scene objects are firstly rendered to render target. With the help of screen quad, the render target texture is then drawn on the quad with shader-defined effects applied.

+

This tutorial shows how to create a screen quad. It also shows how to create a render target texture and associate it with the quad. Effects are defined as shaders which are applied during rendering the quad with the render target texture attached to it.

+

A simple color inverse example is presented in this tutorial. The effect is written in HLSL and GLSL.

+
Author
Boshen Guan
+

We include all headers and define necessary variables as we have done before.

#include "driverChoice.h"
#include "exampleHelper.h"
#include <irrlicht.h>
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

We write a class derived from IShaderConstantSetCallBack class and implement OnSetConstants callback interface. In this callback, we will set constants used by the shader. In this example, our HLSL shader needs texture size as input in its vertex shader. Therefore, we set texture size in OnSetConstants callback using setVertexShaderConstant function.

IrrlichtDevice* device = 0;
video::ITexture* rt = 0;
class QuadShaderCallBack : public video::IShaderConstantSetCallBack
{
public:
QuadShaderCallBack() : FirstUpdate(true), TextureSizeID(-1), TextureSamplerID(-1)
{ }
virtual void OnSetConstants(video::IMaterialRendererServices* services,
s32 userData)
{
core::dimension2d<u32> size = rt->getSize();
// get texture size array
f32 textureSize[] =
{
(f32)size.Width, (f32)size.Height
};
if ( FirstUpdate )
{
TextureSizeID = services->getVertexShaderConstantID("TextureSize");
TextureSamplerID = services->getPixelShaderConstantID("TextureSampler");
}
// set texture size to vertex shader
services->setVertexShaderConstant(TextureSizeID, reinterpret_cast<f32*>(textureSize), 2);
// set texture for an OpenGL driver
s32 textureLayer = 0;
services->setPixelShaderConstant(TextureSamplerID, &textureLayer, 1);
}
private:
bool FirstUpdate;
s32 TextureSizeID;
s32 TextureSamplerID;
};
class ScreenQuad : public IReferenceCounted
{
public:
ScreenQuad(video::IVideoDriver* driver)
: Driver(driver)
{
// --------------------------------> u
// |[1](-1, 1)----------[2](1, 1)
// | | ( 0, 0) / | (1, 0)
// | | / |
// | | / |
// | | / |
// | | / |
// | | / |
// | | / |
// | | / |
// | | / |
// |[0](-1, -1)---------[3](1, -1)
// | ( 0, 1) (1, 1)
// V
// v

A screen quad is composed of two adjacent triangles with 4 vertices. Vertex [0], [1] and [2] create the first triangle and Vertex [0], [2] and [3] create the second one. To map texture on the quad, UV coordinates are assigned to the vertices. The origin of UV coordinate locates on the top-left corner. And the value of UVs range from 0 to 1.

// define vertices array
Vertices[0] = irr::video::S3DVertex(-1.0f, -1.0f, 0.0f, 1, 1, 0, irr::video::SColor(0,255,255,255), 0.0f, 1.0f);
Vertices[1] = irr::video::S3DVertex(-1.0f, 1.0f, 0.0f, 1, 1, 0, irr::video::SColor(0,255,255,255), 0.0f, 0.0f);
Vertices[2] = irr::video::S3DVertex( 1.0f, 1.0f, 0.0f, 1, 1, 0, irr::video::SColor(0,255,255,255), 1.0f, 0.0f);
Vertices[3] = irr::video::S3DVertex( 1.0f, -1.0f, 0.0f, 1, 1, 0, irr::video::SColor(0,255,255,255), 1.0f, 1.0f);
// define indices for triangles
Indices[0] = 0;
Indices[1] = 1;
Indices[2] = 2;
Indices[3] = 0;
Indices[4] = 2;
Indices[5] = 3;
// turn off lighting as default
Material.setFlag(video::EMF_LIGHTING, false);
// set texture warp settings to clamp to edge pixel
for (u32 i = 0; i < video::MATERIAL_MAX_TEXTURES; i++)
{
Material.TextureLayer[i].TextureWrapU = video::ETC_CLAMP_TO_EDGE;
Material.TextureLayer[i].TextureWrapV = video::ETC_CLAMP_TO_EDGE;
}
}
virtual ~ScreenQuad() {}
virtual void render()
{
// set the material of screen quad
Driver->setMaterial(Material);
// set matrices to fit the quad to full viewport
Driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
Driver->setTransform(video::ETS_VIEW, core::IdentityMatrix);
Driver->setTransform(video::ETS_PROJECTION, core::IdentityMatrix);
// draw screen quad
Driver->drawVertexPrimitiveList(Vertices, 4, Indices, 2);
}
virtual void setMaterialFlag(video::E_MATERIAL_FLAG flag, bool newvalue)
{
Material.setFlag(flag, newvalue);
}
void setMaterialTexture(u32 textureLayer, video::ITexture* texture)
{
Material.setTexture(textureLayer, texture);
}
virtual void setMaterialType(video::E_MATERIAL_TYPE newType)
{
Material.MaterialType = newType;
}
private:
video::IVideoDriver *Driver;
video::S3DVertex Vertices[4];
u16 Indices[6];
video::SMaterial Material;
};

We start up the engine just like before. Then shader programs are selected according to the driver type.

int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// create device
device = createDevice(driverType, core::dimension2d<u32>(640, 480));
if (device == 0)
return 1; // could not create selected driver.
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();

In this example, high level post processing shaders are loaded for both Direct3D and OpenGL drivers. File pp_d3d9.hlsl is for Direct3D 9, and pp_opengl.frag/pp_opengl.vert are for OpenGL.

const io::path mediaPath = getExampleMediaPath();
io::path vsFileName; // filename for the vertex shader
io::path psFileName; // filename for the pixel shader
switch(driverType)
{
case video::EDT_DIRECT3D9:
psFileName = mediaPath + "pp_d3d9.hlsl";
vsFileName = psFileName; // both shaders are in the same file
break;
case video::EDT_OPENGL:
psFileName = mediaPath + "pp_opengl.frag";
vsFileName = mediaPath + "pp_opengl.vert";
break;
}

Check for hardware capability of executing the corresponding shaders on selected renderer. This is not necessary though.

if (!driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1) &&
!driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1))
{
device->getLogger()->log("WARNING: Pixel shaders disabled "\
"because of missing driver/hardware support.");
psFileName = "";
}
if (!driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1) &&
!driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1))
{
device->getLogger()->log("WARNING: Vertex shaders disabled "\
"because of missing driver/hardware support.");
vsFileName = "";
}

An animated mesh is loaded to be displayed. As in most examples, we'll take the fairy md2 model.

// load and display animated fairy mesh
scene::IAnimatedMeshSceneNode* fairy = smgr->addAnimatedMeshSceneNode(
smgr->getMesh(mediaPath + "faerie.md2"));
if (fairy)
{
fairy->setMaterialTexture(0,
driver->getTexture(mediaPath + "faerie2.bmp")); // set diffuse texture
fairy->setMaterialFlag(video::EMF_LIGHTING, false); // disable dynamic lighting
fairy->setPosition(core::vector3df(-10,0,-100));
fairy->setMD2Animation ( scene::EMAT_STAND );
}
// add scene camera
smgr->addCameraSceneNode(0, core::vector3df(10,10,-80),
core::vector3df(-10,10,-100));

We create a render target texture (RTT) with the same size as frame buffer. Instead of rendering the scene directly to the frame buffer, we firstly render it to this RTT. Post processing is then applied based on this RTT. RTT size needs not to be the same with frame buffer though. However in this example, we expect the result of rendering to RTT to be consistent with the result of rendering directly to the frame buffer. Therefore, the size of RTT keeps the same with frame buffer.

// create render target
if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET))
{
rt = driver->addRenderTargetTexture(core::dimension2d<u32>(640, 480), "RTT1");
}
else
{
device->getLogger()->log("Your hardware or this renderer is not able to use the "\
"render to texture feature. RTT Disabled.");
}

Post processing is achieved by rendering a screen quad with this RTT (with previously rendered result) as a texture on the quad. A screen quad is geometry of flat plane composed of two adjacent triangles covering the entire area of viewport. In this pass of rendering, RTT works just like a normal texture and is drawn on the quad during rendering. We can then take control of this rendering process by applying various shader-defined materials to the quad. In other words, we can achieve different effect by writing different shaders. This process is called post processing because it normally does not rely on scene geometry. The inputs of this process are just textures, or in other words, just images. With the help of screen quad, we can draw these images on the screen with different effects. For example, we can adjust contrast, make grayscale, add noise, do more fancy effect such as blur, bloom, ghost, or just like in this example, we invert the color to produce negative image. Note that post processing is not limited to use only one texture. It can take multiple textures as shader inputs to provide desired result. In addition, post processing can also be chained to produce compound result.

// we create a screen quad
ScreenQuad *screenQuad = new ScreenQuad(driver);
// turn off mip maps and bilinear filter since we do not want interpolated result
screenQuad->setMaterialFlag(video::EMF_USE_MIP_MAPS, false);
screenQuad->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
// set quad texture to RTT we just create
screenQuad->setMaterialTexture(0, rt);

Let's create material for the quad. Like in other example, we create material using IGPUProgrammingServices and call addShaderMaterialFromFiles, which returns a material type identifier.

// create materials
video::IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
s32 ppMaterialType = 0;
if (gpu)
{
// We write a QuadShaderCallBack class that implements OnSetConstants
// callback of IShaderConstantSetCallBack class at the beginning of
// this tutorial. We set shader constants in this callback.
// create an instance of callback class
QuadShaderCallBack* mc = new QuadShaderCallBack();
// create material from post processing shaders
ppMaterialType = gpu->addHighLevelShaderMaterialFromFiles(
vsFileName, "vertexMain", video::EVST_VS_1_1,
psFileName, "pixelMain", video::EPST_PS_1_1, mc);
mc->drop();
}
// set post processing material type to the quad
screenQuad->setMaterialType((video::E_MATERIAL_TYPE)ppMaterialType);

Now draw everything. That's all.

int lastFPS = -1;
while(device->run())
{
if (device->isWindowActive())
{
driver->beginScene(true, true, video::SColor(255,0,0,0));
if (rt)
{
// draw scene into render target
// set render target to RTT
driver->setRenderTarget(rt, true, true, video::SColor(255,0,0,0));
// draw scene to RTT just like normal rendering
smgr->drawAll();
// after rendering to RTT, we change render target back
driver->setRenderTarget(0, true, true, video::SColor(255,0,0,0));
// render screen quad to apply post processing
screenQuad->render();
}
else
{
// draw scene normally
smgr->drawAll();
}
driver->endScene();
int fps = driver->getFPS();
if (lastFPS != fps)
{
core::stringw str = L"Irrlicht Engine - Post processing example [";
str += driver->getName();
str += "] FPS:";
str += fps;
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}
}
// do not forget to manually drop the screen quad
screenQuad->drop();
device->drop();
return 0;
}
+ + +

 

+ + diff --git a/examples/28.CubeMapping/tutorial.html b/examples/28.CubeMapping/tutorial.html new file mode 100644 index 00000000..30a5dd1b --- /dev/null +++ b/examples/28.CubeMapping/tutorial.html @@ -0,0 +1,213 @@ + + + + + + + +Tutorial 28: CubeMapping + + + + +
+ + + +
+
+
+
Tutorial 28: CubeMapping
+
+
+
+028shot.jpg +
+

Shows usage of cubemap textures and how to do some simple environment mapping. Cubemap textures have images for all 6 directions of a cube in a single texture. Environment is used to reflect the environment around an object onto the object. Cubemaps only work with shader materials which are written to support cube mapping.

+
Author
Michael Zeilfelder, based on EnvCubeMap example from irrSpintz engine.
+

Start with the usual includes.

#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
using namespace irr;

A callback class for our cubemap shader. We need a shader material which maps the cubemap texture to the polygon vertices of objects.

class CubeMapReflectionCallback : public video::IShaderConstantSetCallBack
{
public:
CubeMapReflectionCallback(scene::ISceneManager* smgr, int styleUVW)
: SceneMgr(smgr)
, StyleUVW(styleUVW), Roughness(0.f)
, styleUvwID(-1) , worldViewProjID(-1), worldID(-1), cameraPosID(-1)
{}

Setting the style to map vertex UV-coordinates to the cubemap textures.

    +
  • Specular style is typically used for mirrors and highlight reflections.
  • +
  • Diffuse style is commonly used in image based lighting calculations and often in combination with a higher roughness. Think of it as the sum of all light which reaches a point on your object.
  • +
  • Using model vertices directly for UV's is just nice for testing sometimes. Maybe has more uses? Experiment around :-)
    void SetStyleUVW(int style)
    {
    StyleUVW = style;
    }
    int GetStyleUVW() const
    {
    return StyleUVW;
    }
    We could also call this sharpness as the rougher a material the less sharp the reflections of a cubemap are (light for rough materials spreads out more while smooth materials reflect it more like a mirror). Roughness is calculated using the mipmaps of the cubemap texture. Note that rendertarget cubemap textures won't have mipmaps, so unfortunately it won't work for those. Also currently only OpenGL is able to interpolate seamless over cubemap borders. On Direct3D9 you will only smooth per side, but not over side-borders.
    void SetRoughness(float roughness)
    {
    Roughness = roughness;
    }
    float getRoughness() const
    {
    return Roughness;
    }
    Typical code which passes a few values from c++ to shader.
    virtual void OnSetMaterial(const video::SMaterial& material)
    {}
    virtual void OnSetConstants(video::IMaterialRendererServices* services, s32 userData)
    {
    video::IVideoDriver* driver = services->getVideoDriver();
    if ( worldViewProjID < 0 ) // first update
    {
    styleUvwID = services->getVertexShaderConstantID("StyleUVW");
    if( driver->getDriverType() == video::EDT_DIRECT3D9 )
    {
    worldViewProjID = services->getVertexShaderConstantID("WorldViewProj");
    }
    worldID = services->getVertexShaderConstantID("World");
    cameraPosID = services->getVertexShaderConstantID("CameraPos");
    roughnessID = services->getPixelShaderConstantID("Roughness");
    }
    services->setVertexShaderConstant(styleUvwID, &StyleUVW, 1 );
    irr::core::matrix4 world = driver->getTransform(irr::video::ETS_WORLD);
    services->setVertexShaderConstant(worldID, world.pointer(), 16);
    if( driver->getDriverType() == video::EDT_DIRECT3D9 )
    {
    irr::core::matrix4 worldViewProj;
    worldViewProj = driver->getTransform(irr::video::ETS_PROJECTION);
    worldViewProj *= driver->getTransform(irr::video::ETS_VIEW);
    worldViewProj *= world;
    services->setVertexShaderConstant(worldViewProjID, worldViewProj.pointer(), 16);
    }
    core::vector3df cameraPos = SceneMgr->getActiveCamera()->getAbsolutePosition();
    services->setVertexShaderConstant(cameraPosID, &cameraPos.X, 3 );
    services->setPixelShaderConstant(roughnessID, &Roughness, 1 );
    }
    private:
    scene::ISceneManager* SceneMgr;
    int StyleUVW; // 0 = specular, 1=diffuse, 2 = use model vertex coordinates for uvw.
    float Roughness; // cubemap 0 = specular ... highest value depends on number of mipmaps in the texture
    irr::s32 styleUvwID;
    irr::s32 worldViewProjID;
    irr::s32 worldID;
    irr::s32 cameraPosID;
    irr::s32 roughnessID;
    };
    To keep the example compact our event-receiver acts also like a main application class. So it handles user input, updates the dynamic parts of the UI and it keeps some 3d nodes around.
    class MyEventReceiver : public IEventReceiver
    {
    public:
    MyEventReceiver() : Driver(0), Shader(0)
    ,BackgroundSkybox(0), BackgroundCube(0)
    , CubemapUpdates(2)
    , CurrentStyleUVW(0), CurrentRoughness(0)
    , NeedCubemapUpdate(true)
    {
    StyleNamesUVW.push_back( L"specular" );
    StyleNamesUVW.push_back( L"diffuse" );
    StyleNamesUVW.push_back( L"model coordinates" );
    }
    // Handle the key input
    virtual bool OnEvent(const SEvent& event)
    {
    if (event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown == false)
    {
    switch(event.KeyInput.Key )
    {
    case KEY_SPACE:
    // Switch between different texture mapping styles
    if ( Shader )
    {
    Shader->SetStyleUVW((Shader->GetStyleUVW()+1)%StyleNamesUVW.size());
    updateStyleUVW();
    }
    break;
    case KEY_KEY_B:
    // Switch between our 2 different backgrounds
    if ( BackgroundSkybox && BackgroundCube )
    {
    if ( BackgroundSkybox->isVisible() )
    {
    BackgroundSkybox->setVisible(false);
    BackgroundCube->setVisible(true);
    }
    else
    {
    BackgroundSkybox->setVisible(true);
    BackgroundCube->setVisible(false);
    }
    NeedCubemapUpdate = true;
    }
    break;
    case KEY_KEY_I:
    // Show/hide the info text nodes
    for (u32 i=0; i<InfoTextNodes.size(); ++i )
    InfoTextNodes[i]->setVisible(!InfoTextNodes[i]->isVisible());
    break;
    case KEY_KEY_S:
    // Enable/disable seamless smoothing of mipmaps over cube borders
    if ( Driver )
    {
    Driver->disableFeature(video::EVDF_TEXTURE_CUBEMAP_SEAMLESS, Driver->queryFeature(video::EVDF_TEXTURE_CUBEMAP_SEAMLESS) );
    updateSeamless();
    }
    break;
    case KEY_KEY_U:
    // Switch dynamic cubemap updates on/off.
    CubemapUpdates = (CubemapUpdates+1) % 3;
    updateCubemapUpdates();
    break;
    case KEY_PLUS:
    case KEY_ADD:
    // Make material rougher
    if ( Shader )
    {
    Shader->SetRoughness( Shader->getRoughness() + 0.5f );
    updateRoughness();
    }
    break;
    case KEY_MINUS:
    case KEY_SUBTRACT:
    {
    // Make material smoother
    if ( Shader )
    {
    float roughness = Shader->getRoughness() - 0.5f;
    if ( roughness >= 0.f )
    {
    Shader->SetRoughness(roughness);
    updateRoughness();
    }
    }
    break;
    }
    default:
    break;
    }
    }
    return false;
    }
    // Some helper functions to update the UI
    void updateStyleUVW()
    {
    if ( CurrentStyleUVW && Shader)
    CurrentStyleUVW->setText(StyleNamesUVW[Shader->GetStyleUVW()].c_str());
    }
    void updateRoughness()
    {
    if ( CurrentRoughness && Shader )
    {
    CurrentRoughness->setText( irr::core::stringw(Shader->getRoughness()).c_str() );
    }
    }
    void updateSeamless()
    {
    if ( CurrentSeamlessCubemap && Driver )
    {
    CurrentSeamlessCubemap->setText( Driver->queryFeature(video::EVDF_TEXTURE_CUBEMAP_SEAMLESS) ? L"ON" : L"OFF" );
    }
    }
    void updateCubemapUpdates()
    {
    if ( CurrentCubemapUpdates )
    {
    switch ( CubemapUpdates )
    {
    case 0: CurrentCubemapUpdates->setText( L"static"); break;
    case 1: CurrentCubemapUpdates->setText( L"dynamic" ); break;
    case 2: CurrentCubemapUpdates->setText( L"dynamic+mips" ); break;
    }
    }
    }
    // Check if the cubemap textures should be updated with new screenshots
    // return 0 for no update, 1 for update, 2 for update and fix mip-maps
    int checkCubemapUpdate()
    {
    if ( NeedCubemapUpdate || CubemapUpdates == 2)
    {
    NeedCubemapUpdate = false;
    return 2;
    }
    return CubemapUpdates;
    }
    // Add some text-node floating above it's parent node.
    void addInfoTextNode(irr::gui::IGUIFont* font, const wchar_t* text, irr::scene::ISceneNode* parent)
    {
    if ( parent )
    {
    const video::SColor infoTextCol(250, 70, 90, 90);
    core::dimension2du dim(font->getDimension(text));
    core::dimension2df dimf((f32)dim.Width, (f32)dim.Height);
    scene::IBillboardTextSceneNode* infoNode = parent->getSceneManager()->addBillboardTextSceneNode( font, text, parent, dimf, core::vector3df(0, 120, 0), -1, infoTextCol, infoTextCol);
    InfoTextNodes.push_back(infoNode);
    }
    }
    irr::video::IVideoDriver* Driver;
    CubeMapReflectionCallback* Shader;
    scene::ISceneNode* BackgroundSkybox;
    scene::ISceneNode* BackgroundCube;
    irr::core::array<scene::ISceneNode*> InfoTextNodes;
    int CubemapUpdates; // 0 = static, 1 = dynamic, 2 = dynamic with rtt
    irr::core::array<irr::core::stringw> StyleNamesUVW;
    irr::gui::IGUIStaticText* CurrentStyleUVW;
    irr::gui::IGUIStaticText* CurrentRoughness;
    irr::gui::IGUIStaticText* CurrentSeamlessCubemap;
    irr::gui::IGUIStaticText* CurrentCubemapUpdates;
    private:
    bool NeedCubemapUpdate;
    };
    Workaround for OpenGL's upside-down images. Texture origins (0,0) in OpenGL are usually at the left-bottom instead of the more common left-top image formats. Irrlicht internally uses textures with left-top origin and then corrects the texture-matrices in the fixed-function pipeline. For shader materials it's left to the users to handle those UV-flips for the texture-matrix. Render target textures (RTT's) in OpenGL are rendered with left-bottom origin and Irrlicht can't change that, so all RTT textures in memory are upside-down (unlike all other Irrlicht textures). In the fixed function pipeline Irrlicht handles this by flipping the RTT's texture matrix once more and for shaders it's again left to the users to handle it. Cubemap textures are different from other textures in OpenGL. Each cube side has left-top as the origin. So not flipping Irrlicht textures for those would be fine. Except - OpenGL RTT's still render left-bottom - even when the target is a cubemap RTT. I found no good way around this so far - it just seems messed up as we get a left-handed/right handed coordinate system change that way.
  • +
+

So... the following 2 defines are two different workarounds I found. Both are ugly, which one is better in reality depends probably on the scene. Only use one of those: CUBEMAP_UPSIDE_DOWN_GL_PROJECTION is relatively fast as it just changes the project matrix. The problem is that changing the projection matrix means changing front/backside culling. So every node rendered has to flip the material flags for those.

+

CUBEMAP_USPIDE_DOWN_RTT will change the texture memory itself and flip the image upside-down. While easier to do, this involves texture-locking and is very slow.

#define CUBEMAP_UPSIDE_DOWN_GL_PROJECTION
//#define CUBEMAP_USPIDE_DOWN_RTT
// Flip frontface/backface culling for all nodes
#ifdef CUBEMAP_UPSIDE_DOWN_GL_PROJECTION
void flipCullingFlags(const core::array<scene::ISceneNode*>& nodes)
{
for ( irr::u32 n=0; n < nodes.size(); ++n )
{
scene::ISceneNode* node = nodes[n];
const irr::u32 matCount = node->getMaterialCount();
for ( irr::u32 m=0; m < matCount; ++m)
{
video::SMaterial& mat = node->getMaterial(m);
mat.BackfaceCulling = !mat.BackfaceCulling;
mat.FrontfaceCulling = !mat.FrontfaceCulling;
}
}
}
#endif

Render the environment around a node into a cubemap texture.

void renderEnvironmentCubeMap(irr::video::IVideoDriver* driver, irr::scene::ICameraSceneNode* cubeMapCamera, irr::scene::ISceneNode* cubeCenterNode, video::IRenderTarget* cubeMapRT, video::ITexture* dynamicCubeMapRTT, video::ITexture* depthStencilRTT)
{
// Change to the cubemap camera which has a few specific render-settings
scene::ISceneManager* smgr = cubeMapCamera->getSceneManager();
scene::ICameraSceneNode * oldCam = smgr->getActiveCamera();
smgr->setActiveCamera( cubeMapCamera );

We want to see everything around the center node, so hide the node itself, otherwise it would be in the way. Then set the camera to that node's position.

cubeCenterNode->setVisible( false );
const core::vector3df center( cubeCenterNode->getAbsolutePosition() );
cubeMapCamera->setPosition( center );

Render all 6 directions. Which means simple setting the camera target/up vector to all 6 directions and then render the full scene each time. So yeah - updating an environment cube-map means 6 full renders for each object which needs an environment map. In other words - you generally only want to do that in pre-processing, not in realtime.

const core::vector3df targetVecs[6] = {
core::vector3df(1.f, 0.f, 0.f),
core::vector3df(-1.f, 0.f, 0.f),
core::vector3df(0.f, 1.f, 0.f),
core::vector3df(0.f, -1.f, 0.f),
core::vector3df(0.f, 0.f, 1.f),
core::vector3df(0.f, 0.f, -1.f)
};
const core::vector3df upVecs[6] = {
core::vector3df( 0,1,0 ),
core::vector3df( 0,1,0 ),
core::vector3df( 0,0,-1 ),
core::vector3df( 0,0,1 ),
core::vector3df( 0,1,0 ),
core::vector3df( 0,1,0 )
};
for ( int s=0; s<6; ++s )
{
cubeMapCamera->setUpVector( upVecs[s] );
cubeMapCamera->setTarget( center + targetVecs[s] );
// Here we tell into which side of the cubemap texture we want to write
cubeMapRT->setTexture(dynamicCubeMapRTT, depthStencilRTT, (video::E_CUBE_SURFACE)(video::ECS_POSX + s));
driver->setRenderTargetEx(cubeMapRT, video::ECBF_ALL);
smgr->drawAll();
#ifdef CUBEMAP_USPIDE_DOWN_RTT
// This works because the lock for rtt's always flips in Irrlicht.
// So in this case lock() unlock will result in a flipped texture
// But be warned - it's very, very slow!
driver->setRenderTarget(0); // to avoid accessing active rt
dynamicCubeMapRTT->lock(video::ETLM_READ_WRITE, 0, s, video::ETLF_FLIP_Y_UP_RTT);
dynamicCubeMapRTT->unlock();
#endif
}
//dynamicCubeMapRTT->regenerateMipMapLevels(); // Unfortunately we can't seem to have mipmaps for rtt's
driver->setRenderTarget(0);
cubeCenterNode->setVisible( true );
smgr->setActiveCamera( oldCam );
}

Typical setup at the main start.

int main()
{
// Ask user for driver
video::E_DRIVER_TYPE driverType = driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// Create device
MyEventReceiver eventReceiver;
const core::dimension2d<u32> dimDevice(1024, 768);
IrrlichtDevice* device = createDevice( driverType, dimDevice, 32, false, false, false, &eventReceiver );
if (!device)
return 1;
const io::path mediaPath = getExampleMediaPath();
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* env = device->getGUIEnvironment();
eventReceiver.Driver = driver;
// Set window title
core::stringw strCaption(L"Cubemap example - Irrlicht Engine [");
strCaption += driver->getName();
strCaption += L"]";
device->setWindowCaption(strCaption.c_str());
// set a nicer font
gui::IGUISkin* skin = env->getSkin();
gui::IGUIFont* font = env->getFont(mediaPath + "fonthaettenschweiler.bmp");
if (font)
skin->setFont(font);

Create a shader material for cube mapping

video::IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
s32 cubeMapReflectionMaterial = 0;
if( gpu )
{
// Decide on shader to use based on active driver
irr::io::path vsFileName;
irr::io::path psFileName;
switch( driverType )
{
case video::EDT_DIRECT3D9:
vsFileName = mediaPath + "cubeMapReflectionVS.hlsl";
psFileName = mediaPath + "cubeMapReflectionPS.hlsl";
break;
case video::EDT_OPENGL:
vsFileName = mediaPath + "cubeMapReflection.vert";
psFileName = mediaPath + "cubeMapReflection.frag";
break;
}
CubeMapReflectionCallback* cubeMapCB = new CubeMapReflectionCallback(smgr, 2);
cubeMapReflectionMaterial = gpu->addHighLevelShaderMaterialFromFiles(
vsFileName, "VS", video::EVST_VS_1_1,
psFileName, "PS", video::EPST_PS_3_0,
cubeMapCB, video::EMT_SOLID );
if ( cubeMapReflectionMaterial >= 0 )
eventReceiver.Shader = cubeMapCB;
cubeMapCB->drop();
}
// add fps camera
scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS(0, 100.f, 1.f);
camera->setPosition( core::vector3df( 0,10,-200 ) );
device->getCursorControl()->setVisible(false);

Get 6 images forming a cubemap. The coordinate system used in those images seemed to be different than the one in Irrlicht. I decided to leave it like that because it's pretty common that way. If you get cubemap textures which seem to have x/y/z axis named different you'll just have to experiment until you figured out the correct order.

core::array<video::IImage*> cubeMapImages;
cubeMapImages.push_back(driver->createImageFromFile( mediaPath + "cubemap_posx.jpg" ));
cubeMapImages.push_back(driver->createImageFromFile( mediaPath + "cubemap_negx.jpg" ));
cubeMapImages.push_back(driver->createImageFromFile( mediaPath + "cubemap_posy.jpg" ));
cubeMapImages.push_back(driver->createImageFromFile( mediaPath + "cubemap_negy.jpg" ));
cubeMapImages.push_back(driver->createImageFromFile( mediaPath + "cubemap_posz.jpg" ));
cubeMapImages.push_back(driver->createImageFromFile( mediaPath + "cubemap_negz.jpg" ));

Create a cubemap texture from those images. Note that 6 images become a single texture now.

video::ITexture* cubeMapStaticTex = 0;
cubeMapStaticTex = driver->addTextureCubemap("cm", cubeMapImages[0], cubeMapImages[1], cubeMapImages[2], cubeMapImages[3], cubeMapImages[4], cubeMapImages[5]);
for ( u32 i=0; i<cubeMapImages.size(); ++i )
if ( cubeMapImages[i] )
cubeMapImages[i]->drop();
cubeMapImages.clear();

Create a render target, cubemap render-target-textures and a camera with settings for cube mapping

video::IRenderTarget* cubeMapRT = driver->addRenderTarget();
video::ITexture* dynamicCubeMapRTT = 0;
video::ITexture* depthStencilRTT = 0;
video::ITexture* dynamicCubeMapRTT_intermediate = 0; // just for rendering, but not used in material
video::ITexture* dynamicCubeMapTex = 0; // dynamic and with mipmaps
scene::ICameraSceneNode* cubeMapCamera = 0;
if( driver->queryFeature( video::EVDF_RENDER_TO_TARGET ) )
{
// Create cube map textures and render target cubemap textures.
const u32 dynamicCubeMapSize = 512;
dynamicCubeMapRTT = driver->addRenderTargetTextureCubemap(dynamicCubeMapSize, "cube_rtr");
depthStencilRTT = driver->addRenderTargetTexture(irr::core::dimension2du(dynamicCubeMapSize, dynamicCubeMapSize), "cubemap_ds", irr::video::ECF_D24S8);
dynamicCubeMapRTT_intermediate = driver->addRenderTargetTextureCubemap(dynamicCubeMapSize, "cube_rtr");
dynamicCubeMapTex = driver->addTextureCubemap(dynamicCubeMapSize, "cube_tex");
// Camera for creating an environment cubemap
cubeMapCamera = smgr->addCameraSceneNode();
cubeMapCamera->setFOV(core::PI* 0.5f); // 90° view angle
cubeMapCamera->setAspectRatio(1.f); // it's a cube... all sides have the same length
smgr->setActiveCamera( camera );
}

Add sphere-nodes which will be using the cubemaps as materials. You may also want to experiment with other node-types here!

scene::ISceneNode* sphereNode = 0;
scene::ISceneNode* sphereNode2 = 0;
scene::ISceneNode* sphereNode3 = 0;
scene::IMesh* sphereMesh = smgr->getGeometryCreator()->createSphereMesh(100.f);
if( sphereMesh )
{
// Nothing really special here except they need the shader material to display cubemaps.
sphereNode = smgr->addMeshSceneNode( sphereMesh );
sphereNode->setPosition( core::vector3df(-250,0,0) );
sphereNode->updateAbsolutePosition();
sphereNode->setMaterialFlag( video::EMF_LIGHTING, false );
sphereNode->setMaterialTexture( 0, dynamicCubeMapRTT );
sphereNode->setMaterialType( (video::E_MATERIAL_TYPE)cubeMapReflectionMaterial );
eventReceiver.addInfoTextNode(font, L"Cubemap dynamic rtt, no mip-maps", sphereNode);
if ( dynamicCubeMapTex )
{
sphereNode3 = smgr->addMeshSceneNode( sphereMesh );
sphereNode3->setPosition( core::vector3df(0,0,250) );
sphereNode3->updateAbsolutePosition();
sphereNode3->setMaterialFlag( video::EMF_LIGHTING, false );
sphereNode3->setMaterialTexture( 0, dynamicCubeMapTex );
sphereNode3->getMaterial(0).TextureLayer[0].TrilinearFilter = false; // this is default anyway. It would be faster - but you can only access integer mip-levels - no filtering between mip-levels.
sphereNode3->setMaterialType( (video::E_MATERIAL_TYPE)cubeMapReflectionMaterial );
eventReceiver.addInfoTextNode(font, L"Cubemap dynamic with mip-maps", sphereNode3);
}
if ( cubeMapStaticTex )
{
sphereNode2 = smgr->addMeshSceneNode( sphereMesh );
sphereNode2->setPosition( core::vector3df(250,0,0) );
sphereNode2->updateAbsolutePosition();
sphereNode2->setMaterialFlag( video::EMF_LIGHTING, false );
sphereNode2->setMaterialTexture( 0, cubeMapStaticTex );
sphereNode2->getMaterial(0).TextureLayer[0].TrilinearFilter = true; // this way smoothing happens between different mip-levels.
sphereNode2->setMaterialType( (video::E_MATERIAL_TYPE)cubeMapReflectionMaterial );
eventReceiver.addInfoTextNode(font, L"Cubemap fixed images", sphereNode2);
}
sphereMesh->drop();
}

Add some background which will show up in the environment maps. For first one we use the same textures as used in the spheres. Note the difference between a skybox and a cubemap is that the skybox really uses 6 different textures. While the cubemap uses a single texture created from 6 images.

eventReceiver.BackgroundSkybox = smgr->addSkyBoxSceneNode(
driver->getTexture(mediaPath + "cubemap_posy.jpg"), // top
driver->getTexture(mediaPath + "cubemap_negy.jpg"), // bottom
driver->getTexture(mediaPath + "cubemap_posz.jpg"), // left
driver->getTexture(mediaPath + "cubemap_negz.jpg"), // right
driver->getTexture(mediaPath + "cubemap_posx.jpg"), // front
driver->getTexture(mediaPath + "cubemap_negx.jpg")); // back

Another background for comparison and to make it more obvious when the spheres reflect the environment and when they use static cubemaps.

scene::IMesh * cubeMesh = smgr->getGeometryCreator()->createCubeMesh( core::vector3df(10.f, 10.f, 10.f), scene::ECMT_6BUF_4VTX_NP);
smgr->getMeshManipulator()->scale(cubeMesh, core::vector3df(-1, 1, 1));
if( cubeMesh )
{
smgr->getMeshManipulator()->setVertexColors( cubeMesh->getMeshBuffer(0), video::SColor(255, 240, 10, 10) );
smgr->getMeshManipulator()->setVertexColors( cubeMesh->getMeshBuffer(1), video::SColor(255, 240, 130, 10) );
smgr->getMeshManipulator()->setVertexColors( cubeMesh->getMeshBuffer(2), video::SColor(255, 50, 250, 10) );
smgr->getMeshManipulator()->setVertexColors( cubeMesh->getMeshBuffer(3), video::SColor(255, 70, 10, 250) );
smgr->getMeshManipulator()->setVertexColors( cubeMesh->getMeshBuffer(4), video::SColor(255, 240, 250, 10) );
smgr->getMeshManipulator()->setVertexColors( cubeMesh->getMeshBuffer(5), video::SColor(255, 85, 250, 250) );
eventReceiver.BackgroundCube = smgr->addMeshSceneNode( cubeMesh );
cubeMesh->drop();
eventReceiver.BackgroundCube->setScale( core::vector3df( 200, 200, 200 ) );
eventReceiver.BackgroundCube->setMaterialFlag( video::EMF_LIGHTING, false );
eventReceiver.BackgroundCube->setVisible(false);
}
#ifdef CUBEMAP_UPSIDE_DOWN_GL_PROJECTION
if ( driverType == video::EDT_OPENGL )
{
// Flip projection matrix (note this also flips front/backface culling)
core::matrix4 matProj = cubeMapCamera->getProjectionMatrix();
matProj[4] = -matProj[4];
matProj[5] = -matProj[5];
matProj[6] = -matProj[6];
matProj[7] = -matProj[7];
cubeMapCamera->setProjectionMatrix(matProj);
}
#endif

Add some moving node to show the difference between static/dynamic environment maps

scene::IMeshSceneNode * movingNode = smgr->addCubeSceneNode(30.f);
movingNode->getMaterial(0).Lighting = false;
smgr->getMeshManipulator()->setVertexColors( movingNode->getMesh()->getMeshBuffer(0), video::SColor(255, 230, 200, 150));
scene::ISceneNodeAnimator* circleAnimator = smgr->createFlyCircleAnimator(core::vector3df(-125, -50.f, 125), 300.f, 0.0005f);
movingNode->addAnimator(circleAnimator);
circleAnimator->drop();

Add some UI

if ( eventReceiver.Shader )
{
skin->setColor(gui::EGDC_3D_FACE, video::SColor(50, 160, 120, 120));
u32 top = dimDevice.Height - 200;
const u32 left = dimDevice.Width - 350;
const u32 right = dimDevice.Width - 10;
irr::gui::IGUIStaticText * stextUVW = env->addStaticText(L" Style of generating texture coordinates:\n Change with (space)", core::recti(left, top, right, top+35), false, true, 0, -1, true);
top += 40;
stextUVW->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_UPPERLEFT);
eventReceiver.CurrentStyleUVW = env->addStaticText(L"", core::recti(240,0, 400, 20), false, false, stextUVW);
eventReceiver.updateStyleUVW();
irr::gui::IGUIStaticText * stextRoughness = env->addStaticText(L" Roughness:\n Change with (+) and (-)", core::recti(left, top, right, top+35), false, true, 0, -1, true);
top += 40;
eventReceiver.CurrentRoughness = env->addStaticText( L"", core::recti(240,0, 400, 20), false, false, stextRoughness);
eventReceiver.updateRoughness();
irr::gui::IGUIStaticText * stextSeamlessCupemap = env->addStaticText(L" Seamless cubemap (with roughness):\n Change with (s)", core::recti(left, top, right, top+35), false, true, 0, -1, true);
top += 40;
eventReceiver.CurrentSeamlessCubemap = env->addStaticText( L"", core::recti(240,0, 400, 20), false, false, stextSeamlessCupemap);
eventReceiver.updateSeamless();
irr::gui::IGUIStaticText * stextUpdates = env->addStaticText(L" Cubemap updates:\n Change with (u)", core::recti(left, top, right, top+35), false, true, 0, -1, true);
top += 40;
eventReceiver.CurrentCubemapUpdates = env->addStaticText( L"", core::recti(240,0, 400, 20), false, false, stextUpdates);
eventReceiver.updateCubemapUpdates();
env->addStaticText(L" Change background with (b)", core::recti(left, top, right, top+15), false, true, 0, -1, true);
top += 20;
env->addStaticText(L" Show/hide info nodes with (i)", core::recti(left, top, right, top+15), false, true, 0, -1, true);
}

Main loop

while(device->run())
{
if (device->isWindowActive())
{
driver->beginScene(true, true, video::SColor(255, 127, 127, 255));

Check if we want to update the environment maps. Usually not something you'll do every frame, but either once at the star or maybe updating an environment map once in a while.

int updateCubemaps = eventReceiver.checkCubemapUpdate();
if( dynamicCubeMapRTT && sphereNode && updateCubemaps > 0 )
{
#ifdef CUBEMAP_UPSIDE_DOWN_GL_PROJECTION
core::array<scene::ISceneNode*> allNodes;
if ( driverType == video::EDT_OPENGL )
{

Flipping projection matrix flips front/backface culling. We only have a skybox so in this case this still would be fast, with more objects it's getting more ugly.

smgr->getSceneNodesFromType(scene::ESNT_ANY, allNodes);
flipCullingFlags(allNodes);
}
#endif

If rendered just once then this node has still a white (or even undefined) texture at this point Just hiding it and render the background when rendering the cubemap for the other node is less noticable than having a big white dot in the environment texture. Render order can matter if you want several environment maps in your scene.

if (sphereNode3)
sphereNode3->setVisible(false);
renderEnvironmentCubeMap(driver, cubeMapCamera, sphereNode, cubeMapRT, dynamicCubeMapRTT, depthStencilRTT);
if ( sphereNode3)
{
if ( updateCubemaps == 2 )
{

Our rtt's unfortunately don't have mipmaps (sorry, not sure if we can get that somehow...) So if we want mipmaps in the dynamic cubemap we have to copy it to a non-rtt texture. Warning: Very, very slow. Far slower than just creating an environment map as this will copy the texture from GPU to main memory - copy it to a new texture, create mip-maps and upload the result back to the GPU.

renderEnvironmentCubeMap(driver, cubeMapCamera, sphereNode3, cubeMapRT, dynamicCubeMapRTT_intermediate, depthStencilRTT);
for ( int i=0; i<6; ++i)
{
void * rtData = dynamicCubeMapRTT_intermediate->lock(video::ETLM_READ_ONLY, 0, i, video::ETLF_NONE);
void * tData = dynamicCubeMapTex->lock(video::ETLM_READ_WRITE, 0, i);
memcpy(tData, rtData, dynamicCubeMapTex->getPitch()*dynamicCubeMapTex->getSize().Width);
dynamicCubeMapRTT_intermediate->unlock();
dynamicCubeMapTex->unlock();
dynamicCubeMapTex->regenerateMipMapLevels();
}
}
sphereNode3->setVisible(true);
}
#ifdef CUBEMAP_UPSIDE_DOWN_GL_PROJECTION
if ( driverType == video::EDT_OPENGL )
{
flipCullingFlags(allNodes);
}
#endif
}
smgr->drawAll();
env->drawAll();
driver->endScene();
}
}
device->drop();
return 0;
}
+ + +

 

+ + diff --git a/examples/30.Profiling/tutorial.html b/examples/30.Profiling/tutorial.html new file mode 100644 index 00000000..eb5e8ec9 --- /dev/null +++ b/examples/30.Profiling/tutorial.html @@ -0,0 +1,209 @@ + + + + + + + +Tutorial 30: Profiling + + + + +
+ + + +
+
+
+
Tutorial 30: Profiling
+
+
+
+030shot.jpg +
+

Profiling is used to get runtime information about code code.

+

There exist several independent profiling tools. Examples for free profilers are "gprof" for the GNU toolchain and "very sleepy" from codersnotes for Windows. Proprietary tools are for example "VTune" from Intel or "AMD APP Profiler". Those tools work by sampling the running application regularly to get statistic information about the called functions. The way to use them is to compile your application with special flags to include profiling information (some also work with debug information). They also might allow to profile only certain parts of the code, although most can't do that. The sampling is usually rather time-consuming which means the application will be very slow when collecting the profiling data. It's often useful to start with one of those tools to get an overview over the bottlenecks in your application. Those tools have the advantage that they don't need any modifications inside the code.

+

Once you need to dig deeper the Irrlicht profiler can help you. It works nearly like a stopwatch. You add start/stop blocks into the parts of your code which you need to check and the Irrlicht profiler will give you then the exact times of execution for those parts. And unlike general profiler tools you don't just get average information about the run-time but also worst-cases. Which tends to be information you really for a stable framerate. Also the Irrlicht profiler has a low overhead and affects only the areas which you want to time. So you can profile applications with nearly original speed.

+

Irrlicht itself has such profiling information, which is useful to figure out where the runtime inside the engine is spend. To get that profiling data you need to recompile Irrlicht with IRR_COMPILE_WITH_PROFILING enabled as collecting profiling information is disabled by default for speed reasons.

It's usually a good idea to wrap all your profile code with a define. That way you don't have to worry too much about the runtime profiling itself takes. You can remove the profiling code completely when you release the software by removing a single define.Or sometimes you might want to have several such defines for different areas of your application code.

#define ENABLE_MY_PROFILE // comment out to remove the profiling code
#ifdef ENABLE_MY_PROFILE
// calls code X
#define MY_PROFILE(X) X
#else
// removes the code for X in the pre-processor
#define MY_PROFILE(X)
#endif // IRR_PROFILE
#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

We have the choice between working with fixed and with automatic profiling id's. Here are some fixed ID's we will be using.

enum EProfiles
{
EP_APP_TIME_ONCE,
EP_APP_TIME_UPDATED,
EP_SCOPE1,
EP_SCOPE2,
EP_DRAW_SCENE
};
// For our example scenes
enum EScenes
{
ES_NONE, // no scene set
ES_CUBE,
ES_QUAKE_MAP,
ES_DWARVES,
ES_COUNT // counting how many scenes we have
};

Controlling the profiling display is application specific behavior. We use function keys in our case and play around with all the parameters. In real applications you will likely only need something to make the profiling-display visible/invisible and switch pages while the parameters can be set to fixed values.

class MyEventReceiver : public IEventReceiver
{
public:
// constructor
MyEventReceiver(ISceneManager * smgr) : GuiProfiler(0), IncludeOverview(true), IgnoreUncalled(false), ActiveScene(ES_NONE), SceneManager(smgr) {}
virtual bool OnEvent(const SEvent& event)
{
if (event.EventType == EET_KEY_INPUT_EVENT)
{
if ( event.KeyInput.PressedDown )
{

Catching keys to control the profiling display and the profiler itself

switch ( event.KeyInput.Key )
{
case KEY_F1:
GuiProfiler->setVisible( !GuiProfiler->isVisible() );
break;
case KEY_F2:
GuiProfiler->nextPage(IncludeOverview);
break;
case KEY_F3:
GuiProfiler->previousPage(IncludeOverview);
break;
case KEY_F4:
GuiProfiler->firstPage(IncludeOverview);
break;
case KEY_F5:
IncludeOverview = !IncludeOverview;
GuiProfiler->firstPage(IncludeOverview); // not strictly needed, but otherwise the update won't update
break;
case KEY_F6:

You can set more filters. This one filters out profile data which was never called.

IgnoreUncalled = !IgnoreUncalled;
GuiProfiler->setFilters(IgnoreUncalled ? 1 : 0, 0, 0.f, 0);
break;
case KEY_F7:
GuiProfiler->setShowGroupsTogether( !GuiProfiler->getShowGroupsTogether() );
break;
case KEY_F8:
NextScene();
break;
case KEY_F9:
{
u32 index = 0;
if ( getProfiler().findGroupIndex(index, L"grp runtime") )
{
getProfiler().resetGroup(index);
}
}
break;
case KEY_F10:
{
u32 index = 0;
if ( getProfiler().findDataIndex(index, L"scope 3") )
{
getProfiler().resetDataByIndex(index);
}
}
break;
case KEY_F11:
getProfiler().resetAll();
break;
case KEY_KEY_F:
GuiProfiler->setFrozen(!GuiProfiler->getFrozen());
break;
default:
break;
}
}
}
return false;
}

Some example scenes so we have something to profile

void NextScene()
{
SceneManager->clear();
ActiveScene = (ActiveScene+1) % ES_COUNT;
if ( ActiveScene == 0 )
ActiveScene = ActiveScene+1;
switch ( ActiveScene )
{
case ES_CUBE:
{

Simple scene with cube and light.

MY_PROFILE(CProfileScope p(L"cube", L"grp switch scene");)
SceneManager->addCameraSceneNode (0, core::vector3df(0, 0, 0),
core::vector3df(0, 0, 100),
-1);
SceneManager->addCubeSceneNode (30.0f, 0, -1,
core::vector3df(0, 20, 100),
core::vector3df(45, 45, 45),
core::vector3df(1.0f, 1.0f, 1.0f));
SceneManager->addLightSceneNode(0, core::vector3df(0, 0, 0),
video::SColorf(1.0f, 1.0f, 1.0f),
100.0f);
}
break;
case ES_QUAKE_MAP:
{

Our typical Irrlicht example quake map.

MY_PROFILE(CProfileScope p(L"quake map", L"grp switch scene");)
scene::IAnimatedMesh* mesh = SceneManager->getMesh("20kdm2.bsp");
scene::ISceneNode* node = 0;
if (mesh)
node = SceneManager->addOctreeSceneNode(mesh->getMesh(0), 0, -1, 1024);
if (node)
node->setPosition(core::vector3df(-1300,-144,-1249));
SceneManager->addCameraSceneNodeFPS();
}
break;
case ES_DWARVES:
{

Stress-test Irrlicht a little bit by creating many objects.

MY_PROFILE(CProfileScope p(L"dwarfes", L"grp switch scene");)
scene::IAnimatedMesh* aniMesh = SceneManager->getMesh( getExampleMediaPath() + "dwarf.x" );
if (aniMesh)
{
scene::IMesh * mesh = aniMesh->getMesh (0);
if ( !mesh )
break;

You can never have too many dwarves. So let's make some.

const int nodesX = 30;
const int nodesY = 5;
const int nodesZ = 30;
aabbox3df bbox = mesh->getBoundingBox();
vector3df extent = bbox.getExtent();
const f32 GAP = 10.f;
f32 halfSizeX = 0.5f * (nodesX*extent.X + GAP*(nodesX-1));
f32 halfSizeY = 0.5f * (nodesY*extent.Y + GAP*(nodesY-1));
f32 halfSizeZ = 0.5f * (nodesZ*extent.Z + GAP*(nodesZ-1));
for ( int x = 0; x < nodesX; ++x )
{
irr::f32 gapX = x > 0 ? (x-1)*GAP : 0.f;
irr::f32 posX = -halfSizeX + x*extent.X + gapX;
for ( int y = 0; y < nodesY; ++y )
{
irr::f32 gapY = y > 0 ? (y-1)*GAP : 0.f;
irr::f32 posY = -halfSizeY + y*extent.Y + gapY;
for ( int z=0; z < nodesZ; ++z )
{
irr::f32 gapZ = z > 0 ? (z-1)*GAP : 0.f;
irr::f32 posZ = -halfSizeZ + z*extent.Z + gapZ;
scene::IAnimatedMeshSceneNode * node = SceneManager->addAnimatedMeshSceneNode(aniMesh, NULL, -1, vector3df(posX, posY, posZ) );
node->setMaterialFlag(video::EMF_LIGHTING, false);
}
}
}
irr::scene::ICameraSceneNode * camera = SceneManager->addCameraSceneNodeFPS(0, 20.f, 0.1f );
camera->updateAbsolutePosition();
camera->setTarget( vector3df(0,0,0) );
camera->updateAbsolutePosition();
camera->setPosition(irr::core::vector3df(halfSizeX+extent.X, halfSizeY+extent.Y, halfSizeZ+extent.Z));
camera->updateAbsolutePosition();
}
}
break;
}
}
IGUIProfiler * GuiProfiler;
bool IncludeOverview;
bool IgnoreUncalled;
u32 ActiveScene;
scene::ISceneManager* SceneManager;
};
void recursive(int recursion)
{

As the profiler uses internally counters for start stop and only takes profile data when that counter is zero we count all recursions as a single call. If you want to profile each call on it's own you have to use explicit start/stop calls and stop the profile id right before the recursive call.

MY_PROFILE(CProfileScope p3(L"recursive", L"grp runtime");)
if (recursion > 0 )
recursive(recursion-1);
}
int main()
{

Setup, nothing special here.

video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;

Profiler is independent of the device - so we can time the device setup

MY_PROFILE(s32 pDev = getProfiler().add(L"createDevice", L"grp runtime");)
MY_PROFILE(getProfiler().start(pDev);)
IrrlichtDevice * device = createDevice(driverType, core::dimension2d<u32>(640, 480));
if (device == 0)
{

When working with start/stop you should add a stop to all exit paths. Although in this case it wouldn't matter as we don't do anything with it when we quit here.

MY_PROFILE(getProfiler().stop(pDev);)
return 1; // could not create selected driver.
}
MY_PROFILE(getProfiler().stop(pDev);)
video::IVideoDriver* driver = device->getVideoDriver();
IGUIEnvironment* env = device->getGUIEnvironment();
scene::ISceneManager* smgr = device->getSceneManager();
const io::path mediaPath = getExampleMediaPath();

A map we use for one of our test-scenes.

device->getFileSystem()->addFileArchive(mediaPath + "map-20kdm2.pk3");
MyEventReceiver receiver(smgr);
device->setEventReceiver(&receiver);
receiver.NextScene();

Show some info about the controls used in this example

IGUIStaticText * staticText = env->addStaticText(
L"<F1> to show/hide the profiling display\n"
L"<F2> to show the next page\n"
L"<F3> to show the previous page\n"
L"<F4> to show the first page\n"
L"<F5> to flip between including the group overview\n"
L"<F6> to flip between ignoring and showing uncalled data\n"
L"<F7> to flip between showing 1 group per page or all together\n"
L"<F8> to change our scene\n"
L"<F9> to reset the \"grp runtime\" data\n"
L"<F10> to reset the scope 3 data\n"
L"<F11> to reset all data\n"
L"<f> to freeze/unfreeze the display\n"
, recti(10,10, 250, 140), true, true, 0, -1, true);
staticText->setWordWrap(false);

IGUIProfiler is can be used to show active profiling data at runtime.

receiver.GuiProfiler = env->addProfilerDisplay(core::recti(40, 140, 600, 470));
receiver.GuiProfiler->setDrawBackground(true);

Get a monospaced font - it's nicer when working with rows of numbers.

IGUIFont* font = env->getFont(mediaPath + "fontcourier.bmp");
if (font)
receiver.GuiProfiler->setOverrideFont(font);

Adding ID's has to be done before the start/stop calls. This allows start/stop to be really fast and we still have nice information like names and groups. Groups are created automatically each time an ID with a new group-name is added. Groups exist to sort the display data in a nicer way.

MY_PROFILE(
getProfiler().add(EP_APP_TIME_ONCE, L"full time", L"grp runtime");
getProfiler().add(EP_APP_TIME_UPDATED, L"full time updated", L"grp runtime");
getProfiler().add(EP_SCOPE1, L"scope 1", L"grp runtime");
getProfiler().add(EP_DRAW_SCENE, L"draw scene", L"grp runtime");
)

Two timers which run the whole time. One will be continuously updated the other won't.

MY_PROFILE(getProfiler().start(EP_APP_TIME_ONCE);)
MY_PROFILE(getProfiler().start(EP_APP_TIME_UPDATED);)
s32 lastFPS = -1;
while(device->run() && driver)
{
if (device->isWindowActive())
{

For comparison show the FPS in the title bar

s32 fps = driver->getFPS();
if (lastFPS != fps)
{
core::stringw str = L"FPS: ";
str += fps;
device->setWindowCaption(str.c_str());
lastFPS = fps;
}

Times are only updated on stop() calls. So if we want a long-running timer to update we have to stop() and start() it in between. Note that this will also update the call-counter and is rarely needed.

MY_PROFILE(getProfiler().stop(EP_APP_TIME_UPDATED);)
MY_PROFILE(getProfiler().start(EP_APP_TIME_UPDATED);)

The following CProfileScope's will all do the same thing: they measure the time this loop takes. They call start() when the object is created and call stop() when it is destroyed.

+

The first one creates an ID on it's first call and will do constant string-comparisons for the name. It's the slowest, but most comfortable solution. Use it when you just need to run a quick check without the hassle of setting up id's.

MY_PROFILE(CProfileScope p3(L"scope 3", L"grp runtime");)

Second CProfileScope solution will create a data block on first call. So it's a little bit slower on the first run. But usually that's hardly noticeable.

MY_PROFILE(CProfileScope p2(EP_SCOPE2, L"scope 2", L"grp runtime");)

Last CProfileScope solution is the fastest one. But you must add the id before you can use it like that.

MY_PROFILE(CProfileScope p1(EP_SCOPE1));

Call a recursive function to show how profiler only counts it once.

recursive(5);
driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, SColor(0,200,200,200));

If you want to profile only some lines and not a complete scope then you have to work with start() and stop() calls.

MY_PROFILE(getProfiler().start(EP_DRAW_SCENE);)
smgr->drawAll();
MY_PROFILE(getProfiler().stop(EP_DRAW_SCENE);)

If it doesn't matter if the profiler takes some time you can also be lazy and create id's automatically on the spot:

MY_PROFILE(s32 pEnv = getProfiler().add(L"draw env", L"grp runtime");)
MY_PROFILE(getProfiler().start(pEnv);)
env->drawAll();
MY_PROFILE(getProfiler().stop(pEnv);)
driver->endScene();
}
}

Shutdown.

device->drop();

The profiler is independent of an device - so we can still work with it.

MY_PROFILE(getProfiler().stop(EP_APP_TIME_UPDATED));
MY_PROFILE(getProfiler().stop(EP_APP_TIME_ONCE));

Print a complete overview of the profiling data to the console.

MY_PROFILE(core::stringw output);
MY_PROFILE(getProfiler().printAll(output));
MY_PROFILE(printf("%s", core::stringc(output).c_str() ));
return 0;
}
+ + +

 

+ + diff --git a/include/EGUIAlignment.h b/include/EGUIAlignment.h index 85f81b46..4876503b 100644 --- a/include/EGUIAlignment.h +++ b/include/EGUIAlignment.h @@ -5,6 +5,8 @@ #ifndef __E_GUI_ALIGNMENT_H_INCLUDED__ #define __E_GUI_ALIGNMENT_H_INCLUDED__ +#include "irrTypes.h" + namespace irr { namespace gui @@ -35,4 +37,3 @@ const c8* const GUIAlignmentNames[] = } // namespace irr #endif // __E_GUI_ALIGNMENT_H_INCLUDED__ - diff --git a/include/ICameraSceneNode.h b/include/ICameraSceneNode.h index 69fbae86..a157aac8 100644 --- a/include/ICameraSceneNode.h +++ b/include/ICameraSceneNode.h @@ -72,7 +72,7 @@ namespace scene ISceneManager::addCameraSceneNodeFPS, may want to get this input for changing their position, look at target or whatever. */ - virtual bool OnEvent(const SEvent& event) =0; + virtual bool OnEvent(const SEvent& event) _IRR_OVERRIDE_ =0; //! Sets the look at target of the camera /** If the camera's target and rotation are bound ( @see diff --git a/include/IGUIEnvironment.h b/include/IGUIEnvironment.h index 4bd3db87..e631b1dc 100644 --- a/include/IGUIEnvironment.h +++ b/include/IGUIEnvironment.h @@ -260,10 +260,14 @@ public: Note that it usually works badly to pass the modal screen already as parent when creating a new element. It's better to add that new element later to the modal screen with addChild. \param parent Parent gui element of the modal. + \param blinkMode Bitset of when to blink (can be combined) + 0 = never + 1 = focus changes + 2 = Left mouse button pressed down \return Pointer to the created modal. Returns 0 if an error occurred. This pointer should not be dropped. See IReferenceCounted::drop() for more information. */ - virtual IGUIElement* addModalScreen(IGUIElement* parent) = 0; + virtual IGUIElement* addModalScreen(IGUIElement* parent, int blinkMode = 3) = 0; //! Adds a message box. /** \param caption Text to be displayed the title of the message box. diff --git a/include/IGeometryCreator.h b/include/IGeometryCreator.h index 6083f5b4..6b711fea 100644 --- a/include/IGeometryCreator.h +++ b/include/IGeometryCreator.h @@ -183,6 +183,26 @@ public: const video::SColor& colorBottom=video::SColor(0xffffffff), f32 oblique=0.f) const =0; + //! Create a torus mesh + /** Note: Segments might get reduced to ensure it fits into 16-bit meshbuffer. + With 255 segments for minor and major circle you'll hit the maximum. + When using caps 2 more vertices are added. + Note: UV's for caps are probably not useful + \param majorRadius Starting from mesh center + \param minorRadius Starting from a circle at majorRadius distance around center + \param majorSegments Segments for major circle. Will use at least 3 segments. + \param minorSegments Segments for minor circle. Will use at least 3 segments. + \param angleStart Start major circle between 0 and 360° and < angleEnd + \param angleEnd End major circle between 0 and 360° and > angleStart + \param capEnds When you don't create a full major circle you might want caps + 0 = no caps (default) + Bit 1: add cap at angleStart + Bit 2: add cap at angleEnd + */ + virtual IMesh* createTorusMesh(f32 majorRadius, f32 minorRadius, + u32 majorSegments = 32, u32 minorSegments = 16, + f32 angleStart=0.f, f32 angleEnd=360.f, int capEnds=0) const = 0; + //! Create a volume light mesh. /** \param subdivideU Horizontal patch count. diff --git a/include/IIndexBuffer.h b/include/IIndexBuffer.h index 5599b0fb..6aee1a1d 100644 --- a/include/IIndexBuffer.h +++ b/include/IIndexBuffer.h @@ -7,7 +7,7 @@ #include "IReferenceCounted.h" #include "irrArray.h" - +#include "EHardwareBufferFlags.h" #include "SVertexIndex.h" namespace irr diff --git a/include/ILightManager.h b/include/ILightManager.h index ff667983..297e981b 100644 --- a/include/ILightManager.h +++ b/include/ILightManager.h @@ -8,12 +8,13 @@ #include "IReferenceCounted.h" #include "irrArray.h" +#include "ISceneManager.h" // for E_SCENE_NODE_RENDER_PASS, could probably move that to own header? namespace irr { namespace scene { - class ILightSceneNode; + class ISceneNode; //! ILightManager provides an interface for user applications to manipulate the list of lights in the scene. /** The light list can be trimmed or re-ordered before device/ hardware diff --git a/include/IRenderTarget.h b/include/IRenderTarget.h index ffe00a47..0247f798 100644 --- a/include/IRenderTarget.h +++ b/include/IRenderTarget.h @@ -39,7 +39,7 @@ namespace video //! Returns an array of previously set textures. const core::array& getTexture() const { - return Texture; + return Textures; } //! Returns a of previously set depth / depth-stencil texture. @@ -48,34 +48,49 @@ namespace video return DepthStencil; } + //! Returns an array of active surface for cube textures + const core::array& getCubeSurfaces() const + { + return CubeSurfaces; + } + //! Set multiple textures. /** Set multiple textures for the render target. \param texture Array of texture objects. These textures are used for a color outputs. \param depthStencil Depth or packed depth-stencil texture. This texture is used as depth - or depth-stencil buffer. + or depth-stencil buffer. You can pass getDepthStencil() if you don't want to change it. \param cubeSurfaces When rendering to cube textures, set the surface to be used for each texture. Can be empty otherwise. */ - virtual void setTexture(const core::array& texture, ITexture* depthStencil, const core::array& cubeSurfaces = core::array()) = 0; + void setTexture(const core::array& texture, ITexture* depthStencil, const core::array& cubeSurfaces = core::array()) + { + setTextures(texture.const_pointer(), texture.size(), depthStencil, cubeSurfaces.const_pointer(), cubeSurfaces.size()); + } - //! Set one texture. + //! Sets one texture + depthStencil + //! You can pass getDepthStencil() for depthStencil if you don't want to change that one void setTexture(ITexture* texture, ITexture* depthStencil) { - core::array textureArray(1); - textureArray.push_back(texture); - - setTexture(textureArray, depthStencil); + if ( texture ) + { + setTextures(&texture, 1, depthStencil); + } + else + { + setTextures(0, 0, depthStencil); + } } //! Set one cube surface texture. void setTexture(ITexture* texture, ITexture* depthStencil, E_CUBE_SURFACE cubeSurface) { - core::array textureArray(1); - textureArray.push_back(texture); - - core::array cubeSurfaces(1); - cubeSurfaces.push_back(cubeSurface); - - setTexture(textureArray, depthStencil, cubeSurfaces); + if ( texture ) + { + setTextures(&texture, 1, depthStencil, &cubeSurface, 1); + } + else + { + setTextures(0, 0, depthStencil, &cubeSurface, 1); + } } //! Get driver type of render target. @@ -86,8 +101,12 @@ namespace video protected: + //! Set multiple textures. + // NOTE: working with pointers instead of arrays to avoid unnecessary memory allocations for the single textures case + virtual void setTextures(ITexture* const * textures, u32 numTextures, ITexture* depthStencil, const E_CUBE_SURFACE* cubeSurfaces=0, u32 numCubeSurfaces=0) = 0; + //! Textures assigned to render target. - core::array Texture; + core::array Textures; //! Depth or packed depth-stencil texture assigned to render target. ITexture* DepthStencil; diff --git a/include/ISceneManager.h b/include/ISceneManager.h index 65e81c3d..e0733b19 100644 --- a/include/ISceneManager.h +++ b/include/ISceneManager.h @@ -1261,7 +1261,7 @@ namespace scene See IReferenceCounted::drop() for more information. */ virtual ISceneNodeAnimator* createFollowSplineAnimator(s32 startTime, const core::array< core::vector3df >& points, - f32 speed = 1.0f, f32 tightness = 0.5f, bool loop=true, bool pingpong=false) = 0; + f32 speed = 1.0f, f32 tightness = 0.5f, bool loop=true, bool pingpong=false, bool steer=false) = 0; //! Creates a simple ITriangleSelector, based on a mesh. /** Triangle selectors diff --git a/include/ITexture.h b/include/ITexture.h index f975abd8..3976ba37 100644 --- a/include/ITexture.h +++ b/include/ITexture.h @@ -97,6 +97,14 @@ enum E_TEXTURE_CREATION_FLAG */ ETCF_AUTO_GENERATE_MIP_MAPS = 0x00000100, + //! Enable support for vertex shader texture sampling on some drivers + /** Default is false. + This adds a small costs to all texture switches. + Currently only affects D3D9. + On OpenGL vertex shaders use the same texture unit as pixel shaders, so support there only depends on GL version and not on this flag + */ + ETCF_SUPPORT_VERTEXT_TEXTURE = 0x00000200, + /** This flag is never used, it only forces the compiler to compile these enumeration values to 32 bit. */ ETCF_FORCE_32_BIT_DO_NOT_USE = 0x7fffffff diff --git a/include/IVertexBuffer.h b/include/IVertexBuffer.h index 87603dea..8467d7c7 100644 --- a/include/IVertexBuffer.h +++ b/include/IVertexBuffer.h @@ -7,6 +7,7 @@ #include "IReferenceCounted.h" #include "irrArray.h" +#include "EHardwareBufferFlags.h" #include "S3DVertex.h" namespace irr diff --git a/include/IVideoDriver.h b/include/IVideoDriver.h index 4b0c277e..4416f9aa 100644 --- a/include/IVideoDriver.h +++ b/include/IVideoDriver.h @@ -483,7 +483,7 @@ namespace video example in picture edit programs. To avoid this problem, you could use the makeColorKeyTexture method, which takes the position of a pixel instead a color value. - \param zeroTexels \deprecated If set to true, then any texels that match + \param zeroTexels (deprecated) If set to true, then any texels that match the color key will have their color, as well as their alpha, set to zero (i.e. black). This behavior matches the legacy (buggy) behavior prior to release 1.5 and is provided for backwards compatibility only. @@ -500,7 +500,7 @@ namespace video \param colorKeyPixelPos Position of a pixel with the color key color. Every texel with this color will become fully transparent as described above. - \param zeroTexels \deprecated If set to true, then any texels that match + \param zeroTexels (deprecated) If set to true, then any texels that match the color key will have their color, as well as their alpha, set to zero (i.e. black). This behavior matches the legacy (buggy) behavior prior to release 1.5 and is provided for backwards compatibility only. diff --git a/include/IrrCompileConfig.h b/include/IrrCompileConfig.h index 57081ea2..c41f32df 100644 --- a/include/IrrCompileConfig.h +++ b/include/IrrCompileConfig.h @@ -369,7 +369,7 @@ for Windows based systems. You also have to set #define UNICODE for this to comp #undef _IRR_WCHAR_FILESYSTEM #endif -//! Define _IRR_COMPILE_WITH_JPEGLIB_ to enable compiling the engine using libjpeg. +//! Define _IRR_COMPILE_WITH_LIBJPEG_ to enable compiling the engine using libjpeg. /** This enables the engine to read jpeg images. If you comment this out, the engine will no longer read .jpeg images. */ #define _IRR_COMPILE_WITH_LIBJPEG_ diff --git a/include/SIrrCreationParameters.h b/include/SIrrCreationParameters.h index a169456c..f07cdf7d 100644 --- a/include/SIrrCreationParameters.h +++ b/include/SIrrCreationParameters.h @@ -30,7 +30,7 @@ namespace irr Bits(32), ZBufferBits(24), Fullscreen(false), - WindowResizable(false), + WindowResizable(2), Stencilbuffer(true), Vsync(false), AntiAlias(0), @@ -131,8 +131,9 @@ namespace irr //! Should a non-fullscreen window be resizable. /** Might not be supported by all devices. Ignored when Fullscreen is true. - Default: false */ - bool WindowResizable; + Values: 0 = not resizable, 1 = resizable, 2 = system decides default itself + Default: 2*/ + u8 WindowResizable; //! Specifies if the stencil buffer should be enabled. /** Set this to true, if you want the engine be able to draw diff --git a/include/SLight.h b/include/SLight.h index 60df5f6b..8f725945 100644 --- a/include/SLight.h +++ b/include/SLight.h @@ -6,6 +6,7 @@ #define __S_LIGHT_H_INCLUDED__ #include "SColor.h" +#include "vector3d.h" namespace irr { @@ -98,4 +99,3 @@ struct SLight } // end namespace irr #endif - diff --git a/include/SVertexManipulator.h b/include/SVertexManipulator.h index 3fda59de..3cb1477c 100644 --- a/include/SVertexManipulator.h +++ b/include/SVertexManipulator.h @@ -5,6 +5,7 @@ #ifndef __S_VERTEX_MANIPULATOR_H_INCLUDED__ #define __S_VERTEX_MANIPULATOR_H_INCLUDED__ +#include "matrix4.h" #include "S3DVertex.h" #include "SColor.h" diff --git a/include/SceneParameters.h b/include/SceneParameters.h index 4e246902..6e2aaced 100644 --- a/include/SceneParameters.h +++ b/include/SceneParameters.h @@ -5,6 +5,8 @@ #ifndef __I_SCENE_PARAMETERS_H_INCLUDED__ #define __I_SCENE_PARAMETERS_H_INCLUDED__ +#include "irrTypes.h" + /*! \file SceneParameters.h \brief Header file containing all scene parameters for modifying mesh loading etc. diff --git a/include/fast_atof.h b/include/fast_atof.h index b829eaec..bb1077fe 100644 --- a/include/fast_atof.h +++ b/include/fast_atof.h @@ -13,6 +13,13 @@ namespace irr namespace core { //! Selection of characters which count as decimal point in fast_atof + //! By default Irrlicht considers "." as the decimal point in numbers. + //! But sometimes you might run into situations where floats were written in + //! a local format with another decimal point like ",". + //! Best solution is usually to fix those cases by converting the input. + //! But if you don't have that choice you can set this to ".,". + //! WARNING: This is not thread-safe, so don't change while there's a chance + //! of another thread using fast_atof functions at the same time. // TODO: This should probably also be used in irr::core::string, but // the float-to-string code used there has to be rewritten first. IRRLICHT_API extern irr::core::stringc LOCALE_DECIMAL_POINTS; diff --git a/include/irrArray.h b/include/irrArray.h index b8ffd705..b133b62d 100644 --- a/include/irrArray.h +++ b/include/irrArray.h @@ -231,6 +231,40 @@ public: free_when_destroyed=_free_when_destroyed; } + //! Set (copy) data from given memory block + /** \param newData data to set, must have newSize elements + \param newSize Amount of elements in newData + \param canShrink When true we reallocate the array even it can shrink. + May reduce memory usage, but call is more whenever size changes. + \param newDataIsSorted Info if you pass sorted/unsorted data + */ + void set_data(const T* newData, u32 newSize, bool newDataIsSorted=false, bool canShrink=false) + { + reallocate(newSize, canShrink); + set_used(newSize); + for ( u32 i=0; i& operator=(const array& other) { @@ -290,13 +323,7 @@ public: //! Equality operator bool operator == (const array& other) const { - if (used != other.used) - return false; - - for (u32 i=0; i getClosestPoint(const vector2d& point, bool checkOnlySegments=true) const { diff --git a/include/path.h b/include/path.h index b5379877..6bfa2a0b 100644 --- a/include/path.h +++ b/include/path.h @@ -13,7 +13,9 @@ namespace io { //! Type used for all file system related strings. -/** This type will transparently handle different file system encodings. */ +/** This type will transparently handle different file system encodings. + NOTE: For historical reasons the tool-functions using io::path are all in coreutil.h +*/ typedef core::string path; //! Used in places where we identify objects by a filename, but don't actually work with the real filename diff --git a/scripts/doc/irrlicht/doxygen.cfg b/scripts/doc/irrlicht/doxygen.cfg index 2e80f8cb..4aede634 100644 --- a/scripts/doc/irrlicht/doxygen.cfg +++ b/scripts/doc/irrlicht/doxygen.cfg @@ -1482,7 +1482,9 @@ INCLUDE_FILE_PATTERNS = # undefined via #undef or recursively expanded use the := operator # instead of the = operator. -PREDEFINED = +PREDEFINED = _IRR_COMPILE_WITH_PROFILING_ +PREDEFINED += _IRR_COMPILE_WITH_LEAK_HUNTER_ +PREDEFINED += _IRR_COMPILE_WITH_SDL_DEVICE_ # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. diff --git a/scripts/doc/irrlicht/doxygen_tutorial.cfg b/scripts/doc/irrlicht/doxygen_tutorial.cfg new file mode 100644 index 00000000..2d6d9301 --- /dev/null +++ b/scripts/doc/irrlicht/doxygen_tutorial.cfg @@ -0,0 +1,2442 @@ +# Doxyfile 1.8.13 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = NO + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = NO + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = NO + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = NO + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 0. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = NO + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = NO + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = NO + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = NO + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = NO + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = NO + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = NO + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = NO + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= NO + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = irrlicht + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = NO + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = NO + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = single_tut.txt + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. + +FILE_PATTERNS = *.h \ + *.cpp + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = ../../../media + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = NO + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# cost of reduced performance. This can be particularly helpful with template +# rich C++ code for which doxygen's built-in parser lacks the necessary type +# information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse-libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = NO + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = tut_head.html + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = tut_end.html + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 237 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = YES + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /