System tworzenia roślinności na podstawie danych opisujących region świata, parametry roślinności jaka ma znaleźć się na tym regionie oraz 2D tekstury opisującej jej położenie. System ten wykorzystuje pulę wątków roboczych do realizacji swoich zadań, a ich ilość uzależniona jest od ilości rdzeni procesora. Przedstawione fragmenty pochodzą z projektu Sleeping Village (Outdoor rendering tech demo, 2011)
Przykładowy plik xml definiujący kilka layerów roślinności:
<vegetation> <layer file="l1.png" region="12.8671 -29.9516 120.39 90.3597" density="1" initial_scale="2 2 0.8" scale_x="-0.1 0.1" scale_y="-0.3 1" scale_z="-0.1 0.1" texture="trw_set4_1.png"></layer> <layer file="l2.png" region="12.8671 -29.9516 120.39 90.3597" density="2" initial_scale="2 2 0.8" scale_x="-0.1 0.1" scale_y="-0.3 0.3" scale_z="-0.1 0.1" texture="trw_set4_2.png"></layer> <layer file="l3.png" region="-128.513 -28.6477 -39.6575 6.63476" density="2" initial_scale="1.5 1.5 2" scale_x="-0.1 0.1" scale_y="-0.3 0.6" scale_z="-0.1 0.1" texture="trw_zyto1.png"></layer> <layer file="l4.png" region="-138.273 23.355 -108.628 58.3547" density="2" initial_scale="1.5 1.5 2" scale_x="-0.1 0.1" scale_y="-0.3 0.6" scale_z="-0.1 0.1" texture="trw_zyto1.png"></layer> <layer file="l5.png" region="-184.281 -29.668 119.716 131.612" density="2" initial_scale="1 1 1" scale_x="-0.3 0.3" scale_y="-0.6 0.6" scale_z="-0.3 0.3" texture="trw_paprot.dds"></layer> <layer file="l6.png" region="-184.281 -29.668 119.716 131.612" density="1" initial_scale="0.8 0.8 0.8" scale_x="-0.2 0.2" scale_y="-0.3 0.6" scale_z="-0.2 0.2" texture="trw_set1_1.dds"></layer> </vegetation>
ich przetwarzanie rozpoczyna się poprzez wywołanie metody:
void cVegetationLoader::Load()
cVegetationLoader.h
#pragma once #include "Ogre.h" #include <vector> #include <boost/thread/mutex.hpp> #include <threadpool.hpp> #include "rapidxml.hpp" using namespace boost::threadpool; class HavokUtilities; class cVegetationObject { public: cVegetationObject( Ogre::Entity* pEntity, const Ogre::Vector3& position,const Ogre::Quaternion& orientation, const Ogre::Vector3& scale ): m_pEntity( pEntity ), m_position( position), m_scale( scale ), m_orientation( orientation ) {} Ogre::Entity* m_pEntity; Ogre::Vector3 m_position; Ogre::Vector3 m_scale; Ogre::Quaternion m_orientation; }; class cVegetationLoader { friend class cLayerLoaderThread; public: cVegetationLoader( Ogre::SceneManager* pSceneMgr, Ogre::StaticGeometry* pStaticGeometry ); ~cVegetationLoader(); void Load(); protected: void FitVegetationToGround(); int GetNumberOfCores(); void PreloadTexture( rapidxml::xml_node<>* XMLNode); void ProcessLayers(rapidxml::xml_node<>* XMLNode); void ProcessSingleLayer(rapidxml::xml_node<>* XMLNode); Ogre::String GetAttrib(rapidxml::xml_node<>* XMLNode, const Ogre::String &attrib, const Ogre::String &defaultValue); private: Ogre::StaticGeometry* m_pStaticGeometry; HavokUtilities* m_pCollisionsSystem; Ogre::SceneManager* m_pSceneMgr; Ogre::MaterialPtr m_pVegetationMaterial; int m_iMaximumThreads; bool m_bInitialized; boost::mutex m_mutex; pool* m_pThreadPool; std::list< cVegetationObject* > m_vegetationList; }; class cLayerLoaderThread { public: cLayerLoaderThread( rapidxml::xml_node<>* pXMLNode, cVegetationLoader* pParent ); void Run(); protected: void Finish(); void PrepareLayerData(); void CreateLayer(); void PlaceVegetation( Ogre::Real x, Ogre::Real y ); private: cVegetationLoader* m_pLoader; rapidxml::xml_node<>* m_pXMLNode; int m_iThreadNum; boost::mutex* m_pMutex; boost::thread* m_pBoostThread; class cLayerData { public: Ogre::String m_fileName; Ogre::TexturePtr m_pTexture; Ogre::MaterialPtr m_pVegetationMaterial; class cRegion { public: void Calculate( const Ogre::Vector4& vec, Ogre::TexturePtr pTexture ); Ogre::Vector2 top_left; Ogre::Vector2 bottom_right; Ogre::Real width; Ogre::Real height; Ogre::Real width_factor; Ogre::Real height_factor; } m_Region; int m_Density; Ogre::Vector3 m_InitialScale; class cScaleVariation { public: Ogre::Vector2 x; Ogre::Vector2 y; Ogre::Vector2 z; } m_ScaleVariation; Ogre::String m_TextureName; } m_LayerData; };
cVegetationLoader.cpp
#include "cVegetationLoader.h" #include <boost/thread/thread.hpp> #include <Ogre.h> #include <limits> #include <algorithm> #include "HavokUtilities.hpp" cLayerLoaderThread::cLayerLoaderThread( rapidxml::xml_node<>* pXMLNode, cVegetationLoader* pParent ) { m_pXMLNode = pXMLNode; m_pLoader = pParent; m_iThreadNum = 0; m_pMutex = new boost::mutex(); m_iThreadNum = rand() % 255; } void cLayerLoaderThread::cLayerData::cRegion::Calculate( const Ogre::Vector4& vec, Ogre::TexturePtr pTexture ) { top_left = Ogre::Vector2( vec.x, vec.y ); bottom_right = Ogre::Vector2( vec.z, vec.w ); width = Ogre::Math::Abs( vec.z - vec.x ); height = Ogre::Math::Abs( vec.w - vec.y ); width_factor = width / pTexture->getWidth(); height_factor = height / pTexture->getHeight(); } void cLayerLoaderThread::PrepareLayerData() { m_LayerData.m_fileName = m_pLoader->GetAttrib( m_pXMLNode, "file", "" ); _ASSERT( !m_LayerData.m_fileName.empty() ); if( !m_LayerData.m_fileName.empty() ) { boost::mutex::scoped_lock scoped_lock( *m_pMutex); m_LayerData.m_pTexture = Ogre::TextureManager::getSingleton().load( m_LayerData.m_fileName, "Level" ); m_LayerData.m_pTexture->load(); } m_LayerData.m_Region.Calculate( Ogre::StringConverter::parseVector4( m_pLoader->GetAttrib( m_pXMLNode, "region", "" ) ), m_LayerData.m_pTexture ) ; m_LayerData.m_Density = Ogre::StringConverter::parseInt( m_pLoader->GetAttrib( m_pXMLNode, "density", "" ) ); m_LayerData.m_InitialScale = Ogre::StringConverter::parseVector3( m_pLoader->GetAttrib( m_pXMLNode, "initial_scale", "" ) ); m_LayerData.m_ScaleVariation.x = Ogre::StringConverter::parseVector2( m_pLoader->GetAttrib( m_pXMLNode, "scale_x", "" ) ); m_LayerData.m_ScaleVariation.y = Ogre::StringConverter::parseVector2( m_pLoader->GetAttrib( m_pXMLNode, "scale_y", "" ) ); m_LayerData.m_ScaleVariation.z = Ogre::StringConverter::parseVector2( m_pLoader->GetAttrib( m_pXMLNode, "scale_z", "" ) ); m_LayerData.m_TextureName = m_pLoader->GetAttrib( m_pXMLNode, "texture", "" ); if( !m_LayerData.m_TextureName.empty() ) { boost::mutex::scoped_lock scoped_lock( *m_pMutex); if( !Ogre::MaterialManager::getSingletonPtr()->getByName(m_LayerData.m_TextureName).isNull() ) m_LayerData.m_pVegetationMaterial = Ogre::MaterialManager::getSingletonPtr()->getByName(m_LayerData.m_TextureName); else { m_LayerData.m_pVegetationMaterial = m_pLoader->m_pVegetationMaterial->clone( m_LayerData.m_TextureName ); m_LayerData.m_pVegetationMaterial->getTechnique(0)->getPass(0)->getTextureUnitState( "diffuse_tex" )->setTextureName( m_LayerData.m_TextureName ); Ogre::String normalMapName, ext; Ogre::StringUtil::splitBaseFilename( m_LayerData.m_TextureName, normalMapName, ext ); normalMapName += "_norm.dds"; m_LayerData.m_pVegetationMaterial->getTechnique(0)->getPass(0)->getTextureUnitState( "normal_tex" )->setTextureName( normalMapName ); m_LayerData.m_pVegetationMaterial->compile(); m_LayerData.m_pVegetationMaterial->load(); } } } void cLayerLoaderThread::PlaceVegetation( Ogre::Real x, Ogre::Real y ) { Ogre::Quaternion fixRotation = Ogre::Quaternion( Ogre::Degree( 90 ), Ogre::Vector3::UNIT_X ); for( int i=0; i<m_LayerData.m_Density; i++ ) { Ogre::Entity* vegetationEnt; { boost::mutex::scoped_lock scoped_lock( *m_pMutex); vegetationEnt = m_pLoader->m_pSceneMgr->createEntity( "grass_obj.mesh" ); vegetationEnt->setMaterial( m_LayerData.m_pVegetationMaterial ); } Ogre::Vector3 position = Ogre::Vector3( x, 0, y ); Ogre::Vector3 scale = m_LayerData.m_InitialScale; scale.x += Ogre::Math::RangeRandom( m_LayerData.m_ScaleVariation.x.x, m_LayerData.m_ScaleVariation.x.y ); scale.y = scale.x; scale.z += Ogre::Math::RangeRandom( m_LayerData.m_ScaleVariation.y.x, m_LayerData.m_ScaleVariation.y.y ); Ogre::Real halfX = m_LayerData.m_Region.width_factor / 2; Ogre::Real halfZ = m_LayerData.m_Region.height_factor/ 2; Ogre::Vector3 offset( Ogre::Math::RangeRandom( -halfX, halfX ), 0, Ogre::Math::RangeRandom( -halfZ, halfZ ) ); position += offset; Ogre::Quaternion rotation = fixRotation * Ogre::Quaternion( Ogre::Degree( Ogre::Math::RangeRandom( 5, 355 ) ), Ogre::Vector3::UNIT_Z ); { boost::mutex::scoped_lock scoped_lock( *m_pMutex); m_pLoader->m_vegetationList.push_back( new cVegetationObject( vegetationEnt, position, rotation, scale ) ); } } } void cLayerLoaderThread::CreateLayer() { Ogre::HardwarePixelBufferSharedPtr pixelBuffer = m_LayerData.m_pTexture->getBuffer(); Ogre::Image::Box pixelBox; pixelBuffer->lock(pixelBox,Ogre::HardwareBuffer::HBL_READ_ONLY); const Ogre::PixelBox &pixBox = pixelBuffer->getCurrentLock(); Ogre::uint8* pPixels = static_cast<Ogre::uint8*>(pixBox.data); for( size_t y=0; y<m_LayerData.m_pTexture->getHeight(); y++ ) for( size_t x=0; x<m_LayerData.m_pTexture->getWidth(); x++ ) { int B = *pPixels++; int G = *pPixels++; int R = *pPixels++; int A = *pPixels++; if( R == 255 ) { PlaceVegetation( m_LayerData.m_Region.top_left.x + x * m_LayerData.m_Region.width_factor, m_LayerData.m_Region.top_left.y + y * m_LayerData.m_Region.height_factor); } } pixelBuffer->unlock(); } void cLayerLoaderThread::Run() { PrepareLayerData(); CreateLayer(); Finish(); } void cLayerLoaderThread::Finish() { if( m_pMutex ) delete m_pMutex; } cVegetationLoader::cVegetationLoader( Ogre::SceneManager* pSceneMgr, Ogre::StaticGeometry* pStaticGeometry ) { m_bInitialized = pSceneMgr && pStaticGeometry; _ASSERT( m_bInitialized ); m_pStaticGeometry = pStaticGeometry; m_pSceneMgr = pSceneMgr; m_iMaximumThreads = GetNumberOfCores() * 2; m_pVegetationMaterial = Ogre::MaterialManager::getSingletonPtr()->getByName( "vegetation_base" ); } cVegetationLoader::~cVegetationLoader() { delete m_pThreadPool; while( !m_vegetationList.empty() ) delete m_vegetationList.front(); m_vegetationList.clear(); } Ogre::String cVegetationLoader::GetAttrib(rapidxml::xml_node<>* XMLNode, const Ogre::String &attrib, const Ogre::String &defaultValue) { if(XMLNode->first_attribute(attrib.c_str())) return XMLNode->first_attribute(attrib.c_str())->value(); else return defaultValue; } void cVegetationLoader::Load() { if( !m_bInitialized ) { _ASSERT( m_bInitialized ); return; } rapidxml::xml_document<> XMLDoc; // character type defaults to char rapidxml::xml_node<>* XMLRoot; Ogre::DataStreamPtr stream = Ogre::ResourceGroupManager::getSingleton().openResource( "Vegetation", "Level" ); char* scene = strdup(stream->getAsString().c_str()); XMLDoc.parse<0>(scene); // Grab the scene node XMLRoot = XMLDoc.first_node("vegetation"); m_pCollisionsSystem = new HavokUtilities( true ); m_pCollisionsSystem->LoadScene( "Vegetation.col", "Level"); ProcessLayers( XMLRoot ); delete scene; delete m_pCollisionsSystem; stream.setNull(); } int cVegetationLoader::GetNumberOfCores() { SYSTEM_INFO sysinfo; GetSystemInfo( &sysinfo ); return sysinfo.dwNumberOfProcessors; } void cVegetationLoader::ProcessSingleLayer(rapidxml::xml_node<>* XMLNode) { cLayerLoaderThread* pLayerThread = new cLayerLoaderThread( XMLNode, this ); m_pThreadPool->schedule( boost::bind( &cLayerLoaderThread::Run, pLayerThread ) ); } void cVegetationLoader::PreloadTexture( rapidxml::xml_node<>* XMLNode) { Ogre::String textureName = GetAttrib( XMLNode, "texture", "" ); if( !textureName.empty() ) Ogre::TextureManager::getSingletonPtr()->load( textureName, "General" ); } void cVegetationLoader::ProcessLayers(rapidxml::xml_node<>* XMLNode) { rapidxml::xml_node<>* pElement; m_pThreadPool = new pool( m_iMaximumThreads ); // Process layer pElement = XMLNode->first_node("layer"); while(pElement) { PreloadTexture( pElement ); ProcessSingleLayer(pElement); pElement = pElement->next_sibling("layer"); } m_pThreadPool->wait(); FitVegetationToGround(); m_pStaticGeometry->build(); } void cVegetationLoader::FitVegetationToGround() { m_pCollisionsSystem->getWorld()->lock(); for( std::list< cVegetationObject*>::iterator i = m_vegetationList.begin(); i!=m_vegetationList.end(); i++ ) { hkVector4 from=hkVector4((*i)->m_position.x,0,(*i)->m_position.z); hkpWorldRayCastInput ray; ray.m_from = from; ray.m_to = from; ray.m_from(1) += 100.0f; ray.m_to(1) -= 100.0f; hkpWorldRayCastOutput result; m_pCollisionsSystem->getWorld()->castRay( ray, result ); if( result.hasHit() ) { from.setInterpolate4( ray.m_from, ray.m_to, result.m_hitFraction ); (*i)->m_position.y = from(1); } m_pStaticGeometry->addEntity( (*i)->m_pEntity, (*i)->m_position, (*i)->m_orientation, (*i)->m_scale ); } m_pCollisionsSystem->getWorld()->unlock(); }