mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-31 23:26:41 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			410 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			410 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "world.hpp"
 | |
| 
 | |
| #include <OgreAxisAlignedBox.h>
 | |
| #include <OgreCamera.h>
 | |
| #include <OgreHardwareBufferManager.h>
 | |
| #include <OgreHardwarePixelBuffer.h>
 | |
| #include <OgreRoot.h>
 | |
| 
 | |
| #include <components/esm/loadland.hpp>
 | |
| #include <components/loadinglistener/loadinglistener.hpp>
 | |
| 
 | |
| #include "storage.hpp"
 | |
| #include "quadtreenode.hpp"
 | |
| 
 | |
| namespace
 | |
| {
 | |
| 
 | |
|     bool isPowerOfTwo(int x)
 | |
|     {
 | |
|         return ( (x > 0) && ((x & (x - 1)) == 0) );
 | |
|     }
 | |
| 
 | |
|     int nextPowerOfTwo (int v)
 | |
|     {
 | |
|         if (isPowerOfTwo(v)) return v;
 | |
|         int depth=0;
 | |
|         while(v)
 | |
|         {
 | |
|             v >>= 1;
 | |
|             depth++;
 | |
|         }
 | |
|         return 1 << depth;
 | |
|     }
 | |
| 
 | |
|     Terrain::QuadTreeNode* findNode (const Ogre::Vector2& center, Terrain::QuadTreeNode* node)
 | |
|     {
 | |
|         if (center == node->getCenter())
 | |
|             return node;
 | |
| 
 | |
|         if (center.x > node->getCenter().x && center.y > node->getCenter().y)
 | |
|             return findNode(center, node->getChild(Terrain::NE));
 | |
|         else if (center.x > node->getCenter().x && center.y < node->getCenter().y)
 | |
|             return findNode(center, node->getChild(Terrain::SE));
 | |
|         else if (center.x < node->getCenter().x && center.y > node->getCenter().y)
 | |
|             return findNode(center, node->getChild(Terrain::NW));
 | |
|         else //if (center.x < node->getCenter().x && center.y < node->getCenter().y)
 | |
|             return findNode(center, node->getChild(Terrain::SW));
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| namespace Terrain
 | |
| {
 | |
| 
 | |
|     World::World(Loading::Listener* loadingListener, Ogre::SceneManager* sceneMgr,
 | |
|                      Storage* storage, int visibilityFlags, bool distantLand, bool shaders)
 | |
|         : mStorage(storage)
 | |
|         , mMinBatchSize(1)
 | |
|         , mMaxBatchSize(64)
 | |
|         , mSceneMgr(sceneMgr)
 | |
|         , mVisibilityFlags(visibilityFlags)
 | |
|         , mDistantLand(distantLand)
 | |
|         , mShaders(shaders)
 | |
|         , mVisible(true)
 | |
|         , mLoadingListener(loadingListener)
 | |
|     {
 | |
|         loadingListener->setLabel("Creating terrain");
 | |
|         loadingListener->indicateProgress();
 | |
| 
 | |
|         mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC);
 | |
| 
 | |
|         Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a");
 | |
|         mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual(
 | |
|                     "terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
 | |
|             Ogre::TEX_TYPE_2D, 128, 128, 0, Ogre::PF_A8B8G8R8, Ogre::TU_RENDERTARGET);
 | |
|         mCompositeMapRenderTarget = mCompositeMapRenderTexture->getBuffer()->getRenderTarget();
 | |
|         mCompositeMapRenderTarget->setAutoUpdated(false);
 | |
|         mCompositeMapRenderTarget->addViewport(compositeMapCam);
 | |
| 
 | |
|         mBounds = storage->getBounds();
 | |
| 
 | |
|         int origSizeX = mBounds.getSize().x;
 | |
|         int origSizeY = mBounds.getSize().y;
 | |
| 
 | |
|         // Dividing a quad tree only works well for powers of two, so round up to the nearest one
 | |
|         int size = nextPowerOfTwo(std::max(origSizeX, origSizeY));
 | |
| 
 | |
|         // Adjust the center according to the new size
 | |
|         Ogre::Vector3 center = mBounds.getCenter() + Ogre::Vector3((size-origSizeX)/2.f, (size-origSizeY)/2.f, 0);
 | |
| 
 | |
|         mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
 | |
| 
 | |
|         mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(center.x, center.y), NULL);
 | |
|         buildQuadTree(mRootNode);
 | |
|         loadingListener->indicateProgress();
 | |
|         mRootNode->initAabb();
 | |
|         loadingListener->indicateProgress();
 | |
|         mRootNode->initNeighbours();
 | |
|         loadingListener->indicateProgress();
 | |
|     }
 | |
| 
 | |
|     World::~World()
 | |
|     {
 | |
|         delete mRootNode;
 | |
|         delete mStorage;
 | |
|     }
 | |
| 
 | |
|     void World::buildQuadTree(QuadTreeNode *node)
 | |
|     {
 | |
|         float halfSize = node->getSize()/2.f;
 | |
| 
 | |
|         if (node->getSize() <= mMinBatchSize)
 | |
|         {
 | |
|             // We arrived at a leaf
 | |
|             float minZ,maxZ;
 | |
|             Ogre::Vector2 center = node->getCenter();
 | |
|             if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ))
 | |
|                 node->setBoundingBox(Ogre::AxisAlignedBox(Ogre::Vector3(-halfSize*8192, -halfSize*8192, minZ),
 | |
|                                                           Ogre::Vector3(halfSize*8192, halfSize*8192, maxZ)));
 | |
|             else
 | |
|                 node->markAsDummy(); // no data available for this node, skip it
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (node->getCenter().x - halfSize > mBounds.getMaximum().x
 | |
|                 || node->getCenter().x + halfSize < mBounds.getMinimum().x
 | |
|                 || node->getCenter().y - halfSize > mBounds.getMaximum().y
 | |
|                 || node->getCenter().y + halfSize < mBounds.getMinimum().y )
 | |
|             // Out of bounds of the actual terrain - this will happen because
 | |
|             // we rounded the size up to the next power of two
 | |
|         {
 | |
|             node->markAsDummy();
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // Not a leaf, create its children
 | |
|         node->createChild(SW, halfSize, node->getCenter() - halfSize/2.f);
 | |
|         node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f));
 | |
|         node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f));
 | |
|         node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f);
 | |
|         buildQuadTree(node->getChild(SW));
 | |
|         buildQuadTree(node->getChild(SE));
 | |
|         buildQuadTree(node->getChild(NW));
 | |
|         buildQuadTree(node->getChild(NE));
 | |
| 
 | |
|         // if all children are dummy, we are also dummy
 | |
|         for (int i=0; i<4; ++i)
 | |
|         {
 | |
|             if (!node->getChild((ChildDirection)i)->isDummy())
 | |
|                 return;
 | |
|         }
 | |
|         node->markAsDummy();
 | |
|     }
 | |
| 
 | |
|     void World::update(const Ogre::Vector3& cameraPos)
 | |
|     {
 | |
|         if (!mVisible)
 | |
|             return;
 | |
|         mRootNode->update(cameraPos, mLoadingListener);
 | |
|         mRootNode->updateIndexBuffers();
 | |
|     }
 | |
| 
 | |
|     Ogre::AxisAlignedBox World::getWorldBoundingBox (const Ogre::Vector2& center)
 | |
|     {
 | |
|         if (center.x > mBounds.getMaximum().x
 | |
|                  || center.x < mBounds.getMinimum().x
 | |
|                 || center.y > mBounds.getMaximum().y
 | |
|                 || center.y < mBounds.getMinimum().y)
 | |
|             return Ogre::AxisAlignedBox::BOX_NULL;
 | |
|         QuadTreeNode* node = findNode(center, mRootNode);
 | |
|         Ogre::AxisAlignedBox box = node->getBoundingBox();
 | |
|         box.setExtents(box.getMinimum() + Ogre::Vector3(center.x, center.y, 0) * 8192,
 | |
|                        box.getMaximum() + Ogre::Vector3(center.x, center.y, 0) * 8192);
 | |
|         return box;
 | |
|     }
 | |
| 
 | |
|     Ogre::HardwareVertexBufferSharedPtr World::getVertexBuffer(int numVertsOneSide)
 | |
|     {
 | |
|         if (mUvBufferMap.find(numVertsOneSide) != mUvBufferMap.end())
 | |
|         {
 | |
|             return mUvBufferMap[numVertsOneSide];
 | |
|         }
 | |
| 
 | |
|         int vertexCount = numVertsOneSide * numVertsOneSide;
 | |
| 
 | |
|         std::vector<float> uvs;
 | |
|         uvs.reserve(vertexCount*2);
 | |
| 
 | |
|         for (int col = 0; col < numVertsOneSide; ++col)
 | |
|         {
 | |
|             for (int row = 0; row < numVertsOneSide; ++row)
 | |
|             {
 | |
|                 uvs.push_back(col / static_cast<float>(numVertsOneSide-1)); // U
 | |
|                 uvs.push_back(row / static_cast<float>(numVertsOneSide-1)); // V
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr();
 | |
|         Ogre::HardwareVertexBufferSharedPtr buffer = mgr->createVertexBuffer(
 | |
|                     Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2),
 | |
|                                                 vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
 | |
| 
 | |
|         buffer->writeData(0, buffer->getSizeInBytes(), &uvs[0], true);
 | |
| 
 | |
|         mUvBufferMap[numVertsOneSide] = buffer;
 | |
|         return buffer;
 | |
|     }
 | |
| 
 | |
|     Ogre::HardwareIndexBufferSharedPtr World::getIndexBuffer(int flags, size_t& numIndices)
 | |
|     {
 | |
|         if (mIndexBufferMap.find(flags) != mIndexBufferMap.end())
 | |
|         {
 | |
|             numIndices = mIndexBufferMap[flags]->getNumIndexes();
 | |
|             return mIndexBufferMap[flags];
 | |
|         }
 | |
| 
 | |
|         // LOD level n means every 2^n-th vertex is kept
 | |
|         size_t lodLevel = (flags >> (4*4));
 | |
| 
 | |
|         size_t lodDeltas[4];
 | |
|         for (int i=0; i<4; ++i)
 | |
|             lodDeltas[i] = (flags >> (4*i)) & (0xf);
 | |
| 
 | |
|         bool anyDeltas = (lodDeltas[North] || lodDeltas[South] || lodDeltas[West] || lodDeltas[East]);
 | |
| 
 | |
|         size_t increment = 1 << lodLevel;
 | |
|         assert((int)increment < ESM::Land::LAND_SIZE);
 | |
|         std::vector<short> indices;
 | |
|         indices.reserve((ESM::Land::LAND_SIZE-1)*(ESM::Land::LAND_SIZE-1)*2*3 / increment);
 | |
| 
 | |
|         size_t rowStart = 0, colStart = 0, rowEnd = ESM::Land::LAND_SIZE-1, colEnd = ESM::Land::LAND_SIZE-1;
 | |
|         // If any edge needs stitching we'll skip all edges at this point,
 | |
|         // mainly because stitching one edge would have an effect on corners and on the adjacent edges
 | |
|         if (anyDeltas)
 | |
|         {
 | |
|             colStart += increment;
 | |
|             colEnd -= increment;
 | |
|             rowEnd -= increment;
 | |
|             rowStart += increment;
 | |
|         }
 | |
|         for (size_t row = rowStart; row < rowEnd; row += increment)
 | |
|         {
 | |
|             for (size_t col = colStart; col < colEnd; col += increment)
 | |
|             {
 | |
|                 indices.push_back(ESM::Land::LAND_SIZE*col+row);
 | |
|                 indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment);
 | |
|                 indices.push_back(ESM::Land::LAND_SIZE*col+row+increment);
 | |
| 
 | |
|                 indices.push_back(ESM::Land::LAND_SIZE*col+row);
 | |
|                 indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row);
 | |
|                 indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         size_t innerStep = increment;
 | |
|         if (anyDeltas)
 | |
|         {
 | |
|             // Now configure LOD transitions at the edges - this is pretty tedious,
 | |
|             // and some very long and boring code, but it works great
 | |
| 
 | |
|             // South
 | |
|             size_t row = 0;
 | |
|             size_t outerStep = 1 << (lodDeltas[South] + lodLevel);
 | |
|             for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep)
 | |
|             {
 | |
|                 indices.push_back(ESM::Land::LAND_SIZE*col+row);
 | |
|                 indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row);
 | |
|                 // Make sure not to touch the right edge
 | |
|                 if (col+outerStep == ESM::Land::LAND_SIZE-1)
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep-innerStep)+row+innerStep);
 | |
|                 else
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row+innerStep);
 | |
| 
 | |
|                 for (size_t i = 0; i < outerStep; i += innerStep)
 | |
|                 {
 | |
|                     // Make sure not to touch the left or right edges
 | |
|                     if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep)
 | |
|                         continue;
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*(col)+row);
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row+innerStep);
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row+innerStep);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // North
 | |
|             row = ESM::Land::LAND_SIZE-1;
 | |
|             outerStep = 1 << (lodDeltas[North] + lodLevel);
 | |
|             for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep)
 | |
|             {
 | |
|                 indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row);
 | |
|                 indices.push_back(ESM::Land::LAND_SIZE*col+row);
 | |
|                 // Make sure not to touch the left edge
 | |
|                 if (col == 0)
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row-innerStep);
 | |
|                 else
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*col+row-innerStep);
 | |
| 
 | |
|                 for (size_t i = 0; i < outerStep; i += innerStep)
 | |
|                 {
 | |
|                     // Make sure not to touch the left or right edges
 | |
|                     if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep)
 | |
|                         continue;
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row-innerStep);
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row-innerStep);
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // West
 | |
|             size_t col = 0;
 | |
|             outerStep = 1 << (lodDeltas[West] + lodLevel);
 | |
|             for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep)
 | |
|             {
 | |
|                 indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep);
 | |
|                 indices.push_back(ESM::Land::LAND_SIZE*col+row);
 | |
|                 // Make sure not to touch the top edge
 | |
|                 if (row+outerStep == ESM::Land::LAND_SIZE-1)
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep-innerStep);
 | |
|                 else
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep);
 | |
| 
 | |
|                 for (size_t i = 0; i < outerStep; i += innerStep)
 | |
|                 {
 | |
|                     // Make sure not to touch the top or bottom edges
 | |
|                     if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep)
 | |
|                         continue;
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*col+row);
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i);
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i+innerStep);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // East
 | |
|             col = ESM::Land::LAND_SIZE-1;
 | |
|             outerStep = 1 << (lodDeltas[East] + lodLevel);
 | |
|             for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep)
 | |
|             {
 | |
|                 indices.push_back(ESM::Land::LAND_SIZE*col+row);
 | |
|                 indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep);
 | |
|                 // Make sure not to touch the bottom edge
 | |
|                 if (row == 0)
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+innerStep);
 | |
|                 else
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row);
 | |
| 
 | |
|                 for (size_t i = 0; i < outerStep; i += innerStep)
 | |
|                 {
 | |
|                     // Make sure not to touch the top or bottom edges
 | |
|                     if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep)
 | |
|                         continue;
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep);
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i+innerStep);
 | |
|                     indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
| 
 | |
| 
 | |
|         numIndices = indices.size();
 | |
| 
 | |
|         Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr();
 | |
|         Ogre::HardwareIndexBufferSharedPtr buffer = mgr->createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT,
 | |
|                                                                            numIndices, Ogre::HardwareBuffer::HBU_STATIC);
 | |
|         buffer->writeData(0, buffer->getSizeInBytes(), &indices[0], true);
 | |
|         mIndexBufferMap[flags] = buffer;
 | |
|         return buffer;
 | |
|     }
 | |
| 
 | |
|     void World::renderCompositeMap(Ogre::TexturePtr target)
 | |
|     {
 | |
|         mCompositeMapRenderTarget->update();
 | |
|         target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer());
 | |
|     }
 | |
| 
 | |
|     void World::clearCompositeMapSceneManager()
 | |
|     {
 | |
|         mCompositeMapSceneMgr->destroyAllManualObjects();
 | |
|         mCompositeMapSceneMgr->clearScene();
 | |
|     }
 | |
| 
 | |
|     float World::getHeightAt(const Ogre::Vector3 &worldPos)
 | |
|     {
 | |
|         return mStorage->getHeightAt(worldPos);
 | |
|     }
 | |
| 
 | |
|     void World::applyMaterials(bool shadows, bool splitShadows)
 | |
|     {
 | |
|         mShadows = shadows;
 | |
|         mSplitShadows = splitShadows;
 | |
|         mRootNode->applyMaterials();
 | |
|     }
 | |
| 
 | |
|     void World::setVisible(bool visible)
 | |
|     {
 | |
|         if (visible && !mVisible)
 | |
|             mSceneMgr->getRootSceneNode()->addChild(mRootSceneNode);
 | |
|         else if (!visible && mVisible)
 | |
|             mSceneMgr->getRootSceneNode()->removeChild(mRootSceneNode);
 | |
| 
 | |
|         mVisible = visible;
 | |
|     }
 | |
| 
 | |
|     bool World::getVisible()
 | |
|     {
 | |
|         return mVisible;
 | |
|     }
 | |
| 
 | |
| 
 | |
| }
 |