Disclaimer: This is not an official NxOgre tutorial. However, it has been revised and edited by the creator of NxOgre and BloodyMess, betajaen.
Help: For any problems you encounter while working on these tutorials, as well as for detailed questions and propositions, please visit the NxOgre OgreAddons-Forum.
Table of contents
Units Warning
NxOgre uses the metre-kilogram-second system, so for those of you who are most familiar with the imperial units system please take a moment to familiarize yourself with the International System of Units. Briefly put: NxOgre measures distance in metres, mass in kilograms, and time in seconds.
Newtons are a measurement of force. 1 Newton is the force required to accelerate one kilogram at a rate of one meter per second per second (1 KG·m/s2).
NxOgre makes use of the standard Cartesian coordinate system. So when I refer to the coordinates (10, 12, -8.7), I mean 10.0 metres in the X direction, 12.0 metres in the Y direction and -8.7 metres in the Z direction.
Introduction
In the first tutorial, you have learned where to get BloodyMess, how to build NxOgre, and how to set up your IDE. Therefore, you now have everything you need to start simulating physics in your Ogre applications.
At the end of this tutorial something like the following should be achieved:
The Initial Code
Start a new Ogre and NxOgre project in your -IDE. We will create the necessary code to make Ogre run using the ExampleApplication and ExampleFrameListener classes provided by the Ogre Samples.
The following code will serve as the basis for this tutorial. It should compile and run without errors.
#include "ExampleApplication.h" class BloodyMessTutorial2Listener : public ExampleFrameListener { public: BloodyMessTutorial2Listener(RenderWindow *win, Camera *cam) : ExampleFrameListener(win, cam) { } bool frameStarted(const FrameEvent& evt) { return ExampleFrameListener::frameStarted(evt); } protected: }; class BloodyMessTutorial2 : public ExampleApplication { protected: void createScene() { // Set ambient light mSceneMgr->setAmbientLight(ColourValue(0.5f, 0.5f, 0.5f)); // Create a light Light* l = mSceneMgr->createLight("MainLight"); l->setPosition(20, 80, 50); // Position the camera mCamera->setPosition(0, 20, 80); mCamera->lookAt(0, 20, 0); } // Create a new frame listener void createFrameListener() { mFrameListener = new BloodyMessTutorial2Listener(mWindow, mCamera); mRoot->addFrameListener(mFrameListener); } }; #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 #define WIN32_LEAN_AND_MEAN #include "windows.h" #endif #ifdef __cplusplus extern "C" { #endif #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT) #else int main(int argc, char **argv) #endif { // Create application object BloodyMessTutorial2 app; try { app.go(); } catch(Exception& e) { #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 MessageBoxA(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL); #else std::cerr << "An exception has occurred: " << e.getFullDescription(); #endif } return 0; } #ifdef __cplusplus } #endif
Architecture of a BloodyMess World
The top-most BloodyMess class is the World, of which there can only be one instance. The size of this world is, for all intents and purposes, infinite, however truthfully it is only very, very large. Inside this world exist up to 32 Scenes. Objects in one scene cannot interact with objects in another scene.
In each scene are a large number of Actors, the number of which is only limited by your hardware. These Actors represent a physical object within the World, but they are completely invisible to us. In order to see them, you need to use a Body, which is simply an Actor that renders to your screen using a Render System. BloodyMess comes with several Rendering Systems built in including OpenGL and Ogre, however this does not restrict you from building your own.
Another important element of NxOgre is the TimeController. It controls the passage of time for the Actors within your World, from a frozen state, to that of real time, to a comically fast state.
Our World, Scene, and Render System
First of all, you need to include NxOgre.h and NxOgreOGRE3D.h in order to use BloodyMess and its Ogre specific rendering classes, so place these at the top of your application:
#include <NxOgre.h> #include <NxOgreOGRE3D.h>
Next, in our BloodyMessTutorial2 class, we declare our world, scene, and render system variables in the protected section of our class:
NxOgre::World* mWorld; NxOgre::Scene* mScene; OGRE3DRenderSystem* mRenderSystem;
You should notice that the render system is not part of the NxOgre namespace as it does not belong the the core BloodyMess code because the render systems are interchangeable. In our case, the OGRE3DRenderSystem comes from the NxOgreOGRE3D header.
Within our createScene() function we can then initialize the world:
mWorld = NxOgre::World::createWorld();
Before we initialise this scene, we will create as SceneDescription in our createScene() function, that we will then pass on to the scene initialization function:
NxOgre::SceneDescription sceneDesc; sceneDesc.mGravity = NxOgre::Vec3(0, -9.8f, 0); sceneDesc.mName = "BloodyMessTutorial2"; mScene = mWorld->createScene(sceneDesc);
With this code we have created a scene which uses the Earth's gravity and named the scene. Naming scenes are not important in BloodyMess, and it can indeed function quite well without them, but it is simply a good habit to name them.
With the next three lines, we will also specify some default physical values that are applied to the whole scene:
mScene->getMaterial(0)->setStaticFriction(0.5); mScene->getMaterial(0)->setDynamicFriction(0.5); mScene->getMaterial(0)->setRestitution(0.1);
These control the default frictional values and the bounce (restitution) of the scene. In this case the values you've just set are for a moderate amount of friction with very little bounce.
Now there is only one thing left before we have our scene set up: Declaring the renderer which is, in our case, Ogre.
mRenderSystem = new OGRE3DRenderSystem(mScene);
We are now ready to add objects to our scene.
Adding a Cube
Since our scene is quite empty right now we will now populate it with a cube.
We will create a body with a cube shape. First we need to create another variable, this time of the type OGRE3DBody, which we will initialize in our createScene() function.
OGRE3DBody* mCube;
To actually create this body we have to call the createBody()' function of the render system and pass some parameters:
- the shape of our actor, which is in this case a box of the size 1x1x1 metre
- the position of our new body, which is 20 metres above the center of the scene
- the mesh we want to use to visualize our body
mCube = mRenderSystem->createBody(new NxOgre::Box(1, 1, 1), NxOgre::Vec3(0, 20, 0), "cube.1m.mesh");
If you compile the application now you will see the cube standing somewhere in the scene without moving at all, although we told our scene to have a gravity force. Why is this? We have not told our scene to "move". Or more precisely: we did not advance our TimeController.
Getting Some Motion
To get some motion in the scene we will use our FrameListener to advance our TimeController every frame. Therefore, we have to alter its constructor a bit to get the TimeController, which the listener class has to store as a member. In every call of the frameStarted() function, the TimeController will now be advanced by the delta time (the time since the last frame).
Delete, or modify, the listener class and replace it with the following:
class BloodyMessTutorial2Listener : public ExampleFrameListener { public: BloodyMessTutorial2Listener(RenderWindow *win, Camera *cam) : ExampleFrameListener(win, cam) { mTimeController = NxOgre::TimeController::getSingleton(); } bool frameStarted(const FrameEvent& evt) { mTimeController->advance(evt.timeSinceLastFrame); return ExampleFrameListener::frameStarted(evt); } protected: NxOgre::TimeController* mTimeController; };
Compiling and running the application now, you will see NxOgre simulating physics within your application for the first time in the form of a box falling through your scene.
Physical Interactions
What we did so far with NxOgre could also be easily achieved within Ogre by just moving the entity downward. To fully simulate a physical environment we will now have our box interact with other objects in the scene (what Ogre entities can't) by adding a few more things to our scene.
We will start with adding another cube. But as we don't want both of them just falling straight down, we will add a force to the second so that they collide in midair.
Declare a new OGRE3DBody:
OGRE3DBody* mCubeTwo;
And add the following to the bottom of the createScene() function:
mCubeTwo = mRenderSystem->createBody(new NxOgre::Box(1, 1, 1), NxOgre::Vec3(20, 35, 0), "cube.1m.mesh"); mCubeTwo->addForce(NxOgre::Vec3(-800, -200, 0));
The first line should look familiar to you, but the second needs a little explanation. The addForce() is a function which will will add a force to the box of 800 Newtons in the -X direction, 200 Newtons in the -Y direction and 0 newtons in the Z direction. In other words it will fly off at an angle and hopefully collide with the other box in our scene.
The last step is to create a floor plane, which is an actor, of infinite size that can not be moved:
mScene->createSceneGeometry(new NxOgre::PlaneGeometry(0, NxOgre::Vec3(0, 1, 0)), Matrix44_Identity);
Now, we have an invisible floor plane on which our two cubes will land on. You can enhance the example by adding an Ogre::Plane to visualize the floor, but this has nothing to do with NxOgre and is only making use of simple Ogre calls:
MovablePlane *plane = new MovablePlane("Plane"); plane->d = 0; plane->normal = Vector3::UNIT_Y; Ogre::MeshManager::getSingleton().createPlane("PlaneMesh", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, *plane, 120, 120, 1, 1, true, 1, 3, 3, Vector3::UNIT_Z); Entity *planeEnt = mSceneMgr->createEntity("PlaneEntity", "PlaneMesh"); planeEnt->setMaterialName("Examples/GrassFloor"); Ogre::SceneNode* mPlaneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); mPlaneNode->attachObject(planeEnt); mPlaneNode->scale(100, 100, 100);
Compile and run your application and the two boxes should collide in mid air before finally landing on your floor plane to rest peacefully.
Conclusion
That's it! You have now learned the very basics of BloodyMess. Now, continue on to the next tutorial where we will explore the very helpful Visual Debugger.
Official Wiki and Website - Forum - Ogre Portal for NxOgre
Betajaen's Guide to creating a Bloody mess
Code snippetsbloody mess
General - Particles - Mesh Cooking - Cloths
Spacegaier's Tutorialsbloody mess
Setting up BloodyMess - First steps - Visual Debugger - Volumes & Triggers - Kinematics - Raycasting - Resources and Meshes