Block Land 1

From DaveWiki

Jump to: navigation, search

My recent use of MineCraft has brought back my long standing urge to create a combination Oblivion/Dwarf Fortress/Civilization/MineCraft type of sandbox game world. While I have no illusions that this endeavor will ever turn out to be useful I'll be writing a few articles as I explore this new creation in the hope that it may be of some small use to someone else.

It is important to note that I am very much an Ogre3D newbie. This means that probably much of the Ogre code in these articles will be closer to wrong than right so use them at your own risk. Most of the code for these articles I'll be posting in short snippets. If can't get the snippets to assemble and work correctly and you're completely desperate you can message/e-mail me a request for the entire solution code.

Contents

Starting Out

I'll be using Visual Express 2010 C++ and Ogre3D for this little adventure. VC2010 was trivial to download and install and Ogre3D was similarly simple enough by following the SDK Installation and the Tutorial Framework. This was a pleasant change from the last time I tried Ogre3D a few years ago and there were no official documentation for my compiler and the unofficial ones never worked perfectly.

Our first attempt will just be a naive implementation to display a bunch of blocks on the screen. We'll use the Ogre tutorial framework and just copy and modify the TutorialApplication class to do our bidding. The base class, BaseApplication, has a number of basic features such as an FPS display and WSAD/mouse movement that are useful to keep for now.

There are a variety of ways to display a cube but we'll create one manually (source code as found on the Ogre3D forums):

Ogre::ManualObject* TestBlockLandApplication::createCubeMesh (Ogre::String name, Ogre::String matName) 
{
   Ogre::ManualObject* cube = new Ogre::ManualObject(name);
   cube->begin(matName);
 
   cube->position(0.5f,-0.5f,1.0f);cube->normal(0.408248f,-0.816497f,0.408248f);cube->textureCoord(1,0);
   cube->position(-0.5f,-0.5f,0.0f);cube->normal(-0.408248f,-0.816497f,-0.408248f);cube->textureCoord(0,1);
   cube->position(0.5f,-0.5f,0.0f);cube->normal(0.666667f,-0.333333f,-0.666667f);cube->textureCoord(1,1);
   cube->position(-0.5f,-0.5f,1.0f);cube->normal(-0.666667f,-0.333333f,0.666667f);cube->textureCoord(0,0);
   cube->position(0.5f,0.5f,1.0f);cube->normal(0.666667f,0.333333f,0.666667f);cube->textureCoord(1,0);
   cube->position(-0.5,-0.5,1.0);cube->normal(-0.666667f,-0.333333f,0.666667f);cube->textureCoord(0,1);
   cube->position(0.5,-0.5,1.0);cube->normal(0.408248,-0.816497,0.408248f);cube->textureCoord(1,1);
   cube->position(-0.5,0.5,1.0);cube->normal(-0.408248,0.816497,0.408248);cube->textureCoord(0,0);
   cube->position(-0.5,0.5,0.0);cube->normal(-0.666667,0.333333,-0.666667);cube->textureCoord(0,1);
   cube->position(-0.5,-0.5,0.0);cube->normal(-0.408248,-0.816497,-0.408248);cube->textureCoord(1,1);
   cube->position(-0.5,-0.5,1.0);cube->normal(-0.666667,-0.333333,0.666667);cube->textureCoord(1,0);
   cube->position(0.5,-0.5,0.0);cube->normal(0.666667,-0.333333,-0.666667);cube->textureCoord(0,1);
   cube->position(0.5,0.5,0.0);cube->normal(0.408248,0.816497,-0.408248);cube->textureCoord(1,1);
   cube->position(0.5,-0.5,1.0);cube->normal(0.408248,-0.816497,0.408248);cube->textureCoord(0,0);
   cube->position(0.5,-0.5,0.0);cube->normal(0.666667,-0.333333,-0.666667);cube->textureCoord(1,0);
   cube->position(-0.5,-0.5,0.0);cube->normal(-0.408248,-0.816497,-0.408248);cube->textureCoord(0,0);
   cube->position(-0.5,0.5,1.0);cube->normal(-0.408248,0.816497,0.408248);cube->textureCoord(1,0);
   cube->position(0.5,0.5,0.0);cube->normal(0.408248,0.816497,-0.408248);cube->textureCoord(0,1);
   cube->position(-0.5,0.5,0.0);cube->normal(-0.666667,0.333333,-0.666667);cube->textureCoord(1,1);
   cube->position(0.5,0.5,1.0);cube->normal(0.666667,0.333333,0.666667);cube->textureCoord(0,0);
 
   cube->triangle(0,1,2);      cube->triangle(3,1,0);
   cube->triangle(4,5,6);      cube->triangle(4,7,5);
   cube->triangle(8,9,10);      cube->triangle(10,7,8);
   cube->triangle(4,11,12);   cube->triangle(4,13,11);
   cube->triangle(14,8,12);   cube->triangle(14,15,8);
   cube->triangle(16,17,18);   cube->triangle(16,19,17);
   cube->end();
 
   return cube;
}

This creates an unit cube mesh at the origin. Note that the normals for the cube do not appear to be perfect but are fine for our purposes. To display our cube is a relatively simple matter:

void TestBlockLandApplication::createScene(void)
{
    Ogre::ManualObject* testBox  = createCubeMesh("TestBoxMesh", "");	
    Ogre::SceneNode* headNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
    Ogre::MeshPtr Mesh = testBox->convertToMesh("TestBox");
    Ogre::Entity* pEnt = mSceneMgr->createEntity("TestBox");
 
    headNode->attachObject(pEnt);
}

And the glorious result of all this is:

Image:BlockLand1.png


It may not look great but at least we get a decent frame rate.

World Size

Before we continue with more code we should think about exactly how big our "block world" needs to be. The initial guess, at least mine, might be that for a world to feel large we would need the world X/Y/Z axis sizes to be measured in 1000s or even 10000s of blocks. Lets look at two specific examples from existing games:

  1. Dwarf Fortress -- The standard playable size is around 300x300x50 blocks. The actual world is much bigger but not generated in as much detail (I assume anyways).
  2. MineCraft -- A world I've been playing in was 700x700x128 blocks in size and feels huge. The entire playable area is much, much bigger (x8 Earth's surface area) but it is generated on the fly as you explore it.

The point is that we don't really need a world 1000s of blocks in size for it to be usable but really only 100s. To make it simple for now our goal will be to create a playable/usable 256x256x256 block size world.

More Blocks

There are a variety of ways to adds lots of the same type of object in Ogre3D, three of which are demonstrated in the Instancing sample that is included with Ogre. To keep it as simple as possible we'll be using the StaticGeometry method and create a solid block "world" 256x256x256 blocks in size.

void TestBlockLandApplication::createScene(void)
{
	    //Create a basic green color texture 
	Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().create("BoxColor", "General", true );
	Ogre::Technique* tech = mat->getTechnique(0);
	Ogre::Pass* pass = tech->getPass(0);
	Ogre::TextureUnitState* tex = pass->createTextureUnitState();
	tex->setColourOperationEx(Ogre::LBX_MODULATE, Ogre::LBS_MANUAL, Ogre::LBS_CURRENT, Ogre::ColourValue(0, 0.5, 0));
 
	    //Create the one box and the supporting class objects
	Ogre::ManualObject* testBox  = createCubeMesh("TestBox1", "BoxColor");	
        Ogre::SceneNode* headNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
	Ogre::MeshPtr Mesh = testBox->convertToMesh("TestBox2");
	Ogre::StaticGeometry* pGeom = new Ogre::StaticGeometry (mSceneMgr, "Boxes");
	Ogre::Entity* pEnt = mSceneMgr->createEntity("TestBox2");
 
	pGeom->setRegionDimensions(Ogre::Vector3(300, 300, 300));
 
	    //Create out solid block world
	for (int z = 0; z < 256; ++z)
	{
		for (int y = 0; y < 256; ++y)
		{
			for (int x = 0; x < 256; ++x)
			{
				pGeom->addEntity(pEnt, Ogre::Vector3(x,y,z));
			}
		}
	}
 
	pGeom->build ();
 
        mSceneMgr->setAmbientLight(Ogre::ColourValue(0.5, 0.5, 0.5));
	Ogre::Light* l = mSceneMgr->createLight("MainLight");
        l->setPosition(20,80,50);
}

Unfortunately, when we run this it eventually fails with an exception message. We'll scale back a bit and change the number of z-levels from 256 to 10 and try again. It takes a while but it doesn't crash this time and we end up with a veritable work of art:

Image:BlockLand1b.png


The good news is that we get something to display at a "playable" rate although the bad news is it takes nearly 8 million triangles to do it and we still only have 1/25th of our desired blocks on the screen. The even worse news is:

Image:BlockLand1c.png


If you look at the top line you can see our small little world has allocated more than 1GB of memory and this is a release build! Obviously our naive attempt, although perhaps workable for very small block worlds, is going to fail spectacularly at the scale we wish to use.

Next Time

Experienced Ogre3D users, or even novice 3D programmers, are can undoubtedly count many mistakes in my approach attempted here. Next Time we'll explore a few things we can do to improve our performance situation and not need a supercomputer to render our little block world.

Personal tools