OGRE Wiki
Support and community documentation for Ogre3D
Ogre Forums
ogre3d.org
Log in
Username:
Password:
CapsLock is on.
Remember me (for 1 year)
Log in
Home
Tutorials
Tutorials Home
Basic Tutorials
Intermediate Tutorials
Mad Marx Tutorials
In Depth Tutorials
Older Tutorials
External Tutorials
Cookbook
Cookbook Home
CodeBank
Snippets
Experiences
Ogre Articles
Libraries
Libraries Home
Alternative Languages
Assembling A Toolset
Development Tools
OGRE Libraries
List of Libraries
Tools
Tools Home
DCC Tools
DCC Tutorials
DCC Articles
DCC Resources
Assembling a production pipeline
Development
Development Home
Roadmap
Building Ogre
Installing the Ogre SDK
Setting Up An Application
Ogre Wiki Tutorial Framework
Frequently Asked Questions
Google Summer Of Code
Help Requested
Ogre Core Articles
Community
Community Home
Projects Using Ogre
Recommended Reading
Contractors
Wiki
Immediate Wiki Tasklist
Wiki Ideas
Wiki Guidelines
Article Writing Guidelines
Wiki Styles
Wiki Page Tracker
Ogre Wiki Help
Ogre Wiki Help Overview
Help - Basic Syntax
Help - Images
Help - Pages and Structures
Help - Wiki Plugins
Toolbox
Freetags
Categories
List Pages
Structures
Trackers
Statistics
Rankings
List Galleries
Ogre Lexicon
Comments
History: Selection Buffer
View page
Source of version: 25
(current)
{DIV(type="div",width="50",float="right",bg="#FFFFFF")} {maketoc}{DIV} !{img fileId=2038} Description The following classes are a C++ implementation of a pixel perfect object selection mechanism. It works by rendering a single frame to a texture with custom substituted materials that colorize each entity with a unique color (up to 2{SUP()}24{SUP} colors/objects). The MaterialSwitcher class saves which color belongs to which entity. The SelectionBuffer class then retrieves the correct Entity from the SceneManager. It does not work on StaticGeometry or any other geometry that isn't independent entities, in Ogre terms. These classes are a C++ port of the Python version found [http://wiki.python-ogre.org/index.php/CodeSnippets_Selection_Buffer|here]. The behavior of the C++ version differs from the original Python version in one notable way: render colors are assigned sequentially, rather than randomly. This makes interpreting the debug overlay more visually difficult, but simplifies the code somewhat. %ogre17%%ogre16% !{img fileId=2037} Download Downloadable 7-Zip archive of all eight source files: {img fileId=2034 link=dl2030 title=Download} [dl2030|SelectionBufferSource.7z] !{img fileId=2031} Usage The following fragments won't work as-is. You'll need to put them in appropriate places to integrate them with your code. First the necessary include: {CODE(wrap="1", colors="c++")}#include "SelectionBuffer.h"{CODE} Then initialization: {CODE(wrap="1", colors="c++")}// Hang on to this pointer somewhere (e.g. a member of a user interface manager class) SelectionBuffer *mSelectionBuffer; // SceneManager *sceneManager; (previously created) // RenderWindow *renderWindow; (which can come from calling Ogre::Root::initialise(), among other places) mSelectionBuffer = new SelectionBuffer(sceneManager, renderWindow);{CODE} You'll also need to make sure the material scripts are loaded using the Ogre resource system. (%warning%Sample code not shown.) Then process every frame: {CODE(wrap="1", colors="c++")}// showSelectionDebugOverlay is a boolean whose value is determined by, for instance, a key press if(mSelectionBuffer) mSelectionBuffer->ShowOverlay(showSelectionDebugOverlay); Entity *selectedEntity; // rightMouseClicked is a boolean whose value came from, for instance, // an OIS::MouseListener mouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id) method if(rightMouseClicked) { if(mSelectionBuffer) { // mouseAbsX and mouseAbsY are the absolute coordinates of the click from, for instance, an OIS::MouseState selectedEntity = mSelectionBuffer->OnSelectionClick(mouseAbsX, mouseAbsY); if(selectedEntity != 0) { LogManager::getSingleton().logMessage("Selected " + selectedEntity->getName()); // Do something useful to the selected entity here } else { LogManager::getSingleton().logMessage("Entity not found"); } } }{CODE} !-{img fileId=2031} Source All of the following eight files are complete in themselves and included in the downloadable archive. They only need to be added to your build system to be used. !!{img fileId=2032} Header Files !!!-{img fileId=2032} SelectionBuffer.h {CODE(wrap="1", caption="SelectionBuffer.h", colors="c++")}#ifndef SELECTIONBUFFER_H #define SELECTIONBUFFER_H using namespace Ogre; class MaterialSwitcher; class SelectionRenderListener; class SelectionBuffer { private: SceneManager *mSceneMgr; Camera *mCamera; RenderTarget *mRenderTarget; // This is the material listener - Note: it is controlled by a separate // RenderTargetListener, not applied globally to all targets MaterialSwitcher *mMaterialSwitchListener; SelectionRenderListener *mSelectionTargetListener; TexturePtr mTexture; RenderTexture *mRenderTexture; uint8 *mBuffer; PixelBox *mPixelBox; Overlay *mSelectionDebugOverlay; void CreateRTTOverlays(); void Update(); void UpdateBufferSize(); public: SelectionBuffer(SceneManager *sceneMgr, RenderTarget *renderTarget); ~SelectionBuffer(); Entity *OnSelectionClick(int x, int y); void ShowOverlay(bool show); }; #endif{CODE} !!!-{img fileId=2032} MaterialSwitcher.h {CODE(wrap="1", caption="MaterialSwitcher.h", colors="c++")}#ifndef MATERIALSWITCHER_H #define MATERIALSWITCHER_H using namespace Ogre; class SelectionBuffer; struct cmp_ColourValue { bool operator()(const ColourValue &a, const ColourValue &b) const { return a.getAsBGRA() < b.getAsBGRA(); } }; class MaterialSwitcher : public MaterialManager::Listener { private: typedef std::map<ColourValue, String, cmp_ColourValue> ColorMap; typedef std::map<ColourValue, String, cmp_ColourValue>::const_iterator ColorMapConstIter; String mEmptyString; ColourValue mCurrentColor; String mLastEntity; Technique *mLastTechnique; MaterialSwitcher::ColorMap mColorDict; void getNextColor(); friend SelectionBuffer; public: MaterialSwitcher(); ~MaterialSwitcher(); virtual Technique *handleSchemeNotFound(unsigned short schemeIndex, const String &schemeName, Material *originalMaterial, unsigned short lodIndex, const Renderable *rend); const String &getEntityName(const ColourValue &color) const; void reset(); }; #endif{CODE} !!!-{img fileId=2032} SelectionRenderListener.h {CODE(wrap="1", caption="SelectionRenderListener.h", colors="c++")}#ifndef SELECTIONRENDERLISTENER_H #define SELECTIONRENDERLISTENER_H using namespace Ogre; class MaterialSwitcher; /** We need this attached to the depth target, otherwise we get problems with the compositor MaterialSwitcher should NOT be running all the time - rather only when we're specifically rendering the target that needs it */ class SelectionRenderListener : public RenderTargetListener { private: MaterialSwitcher *mMaterialListener; public: SelectionRenderListener(MaterialSwitcher *switcher); ~SelectionRenderListener(); virtual void preRenderTargetUpdate(const RenderTargetEvent &evt); virtual void postRenderTargetUpdate(const RenderTargetEvent &evt); }; #endif{CODE} !!{img fileId=2031} Source Files !!!-{img fileId=2031} MaterialSwitcher.cpp {CODE(wrap="1", caption="MaterialSwitcher.cpp", colors="c++")}#include "MaterialSwitcher.h" MaterialSwitcher::MaterialSwitcher() : mLastTechnique(0) { mCurrentColor = ColourValue(0.0, 0.0, 0.0); } MaterialSwitcher::~MaterialSwitcher() { } Technique *MaterialSwitcher::handleSchemeNotFound(unsigned short schemeIndex, const Ogre::String &schemeName, Ogre::Material *originalMaterial, unsigned short lodIndex, const Ogre::Renderable *rend) { if(rend) { if(typeid(*rend) == typeid(SubEntity)) { const SubEntity *subEntity = static_cast<const SubEntity *>(rend); if(mLastEntity == subEntity->getParent()->getName()) { const_cast<SubEntity *>(subEntity)->setCustomParameter(1, Vector4(mCurrentColor.r, mCurrentColor.g, mCurrentColor.b, 1.0)); } else { ResourcePtr res = MaterialManager::getSingleton().load("PlainColor", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); mLastTechnique = static_cast<MaterialPtr>(res)->getTechnique(0); getNextColor(); const_cast<SubEntity *>(subEntity)->setCustomParameter(1, Vector4(mCurrentColor.r, mCurrentColor.g, mCurrentColor.b, 1.0)); mLastEntity = subEntity->getParent()->getName(); mColorDict[mCurrentColor] = mLastEntity; } return mLastTechnique; } } return 0; } const String &MaterialSwitcher::getEntityName(const Ogre::ColourValue &color) const { ColorMapConstIter iter = mColorDict.find(color); if(iter != mColorDict.end()) return (*iter).second; else return mEmptyString; } void MaterialSwitcher::getNextColor() { ARGB color = mCurrentColor.getAsARGB(); color++; mCurrentColor.setAsARGB(color); // Alternatively, for an easier to interpret debug overlay with an // increased risk of dictionary collision: // mCurrentColor.setAsARGB(Ogre::Math::UnitRandom()*0x00FFFFFF + 0xFF000000); } void MaterialSwitcher::reset() { mCurrentColor = ColourValue(0.0, 0.0, 0.0); mLastEntity.clear(); mColorDict.clear(); }{CODE} !!!-{img fileId=2031} SelectionRenderListener.cpp {CODE(wrap="1", caption="SelectionRenderListener.cpp", colors="c++")}#include "SelectionRenderListener.h" #include "MaterialSwitcher.h" SelectionRenderListener::SelectionRenderListener(MaterialSwitcher *switcher) : mMaterialListener(switcher) { } SelectionRenderListener::~SelectionRenderListener() { delete mMaterialListener; } void SelectionRenderListener::preRenderTargetUpdate(const RenderTargetEvent &evt) { MaterialManager::getSingleton().addListener(mMaterialListener); } void SelectionRenderListener::postRenderTargetUpdate(const RenderTargetEvent &evt) { MaterialManager::getSingleton().removeListener(mMaterialListener); }{CODE} !!!-{img fileId=2031} SelectionBuffer.cpp {CODE(wrap="1", caption="SelectionBuffer.cpp", colors="c++")}#include <Ogre.h> #include "MaterialSwitcher.h" #include "SelectionRenderListener.h" #include "SelectionBuffer.h" SelectionBuffer::SelectionBuffer(SceneManager *sceneMgr, RenderTarget *renderTarget) : mSceneMgr(sceneMgr), mRenderTarget(renderTarget), mBuffer(0), mPixelBox(0) { mCamera = mSceneMgr->getCamera("MainCamera"); mMaterialSwitchListener = new MaterialSwitcher(); mSelectionTargetListener = new SelectionRenderListener(mMaterialSwitchListener); unsigned int width = mRenderTarget->getWidth(); unsigned int height = mRenderTarget->getHeight(); mTexture = TextureManager::getSingleton().createManual("SelectionPassTex", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, width, height, 0, PF_R8G8B8, TU_RENDERTARGET); mRenderTexture = mTexture->getBuffer()->getRenderTarget(); mRenderTexture->setAutoUpdated(false); mRenderTexture->setPriority(0); mRenderTexture->addViewport(mCamera); mRenderTexture->getViewport(0)->setOverlaysEnabled(false); mRenderTexture->getViewport(0)->setClearEveryFrame(true); mRenderTexture->getViewport(0)->setShadowsEnabled(false); mRenderTexture->addListener(mSelectionTargetListener); mRenderTexture->getViewport(0)->setMaterialScheme("aa"); HardwarePixelBufferSharedPtr pixelBuffer = mTexture->getBuffer(); size_t bufferSize = pixelBuffer->getSizeInBytes(); mBuffer = new uint8[bufferSize]; mPixelBox = new PixelBox(pixelBuffer->getWidth(), pixelBuffer->getHeight(), pixelBuffer->getDepth(), pixelBuffer->getFormat(), mBuffer); CreateRTTOverlays(); } SelectionBuffer::~SelectionBuffer() { TextureManager::getSingleton().unload("SelectionPassTex"); delete mPixelBox; delete[] mBuffer; delete mSelectionTargetListener; delete mMaterialSwitchListener; } void SelectionBuffer::Update() { UpdateBufferSize(); mMaterialSwitchListener->reset(); mRenderTexture->update(); mRenderTexture->copyContentsToMemory(*mPixelBox, RenderTarget::FB_FRONT); } void SelectionBuffer::UpdateBufferSize() { unsigned int width = mRenderTarget->getWidth(); unsigned int height = mRenderTarget->getHeight(); if(width != mRenderTexture->getWidth() || height != mRenderTexture->getHeight()) { TextureManager::getSingleton().unload("SelectionPassTex"); delete[] mBuffer; delete mPixelBox; mTexture = TextureManager::getSingleton().createManual("SelectionPassTex", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, width, height, 0, PF_R8G8B8, TU_RENDERTARGET); mRenderTexture = mTexture->getBuffer()->getRenderTarget(); mRenderTexture->setAutoUpdated(false); mRenderTexture->setPriority(0); mRenderTexture->addViewport(mCamera); mRenderTexture->getViewport(0)->setOverlaysEnabled(false); mRenderTexture->getViewport(0)->setClearEveryFrame(true); mRenderTexture->addListener(mSelectionTargetListener); mRenderTexture->getViewport(0)->setMaterialScheme("aa"); HardwarePixelBufferSharedPtr pixelBuffer = mTexture->getBuffer(); size_t bufferSize = pixelBuffer->getSizeInBytes(); mBuffer = new uint8[bufferSize]; mPixelBox = new PixelBox(pixelBuffer->getWidth(), pixelBuffer->getHeight(), pixelBuffer->getDepth(), pixelBuffer->getFormat(), mBuffer); } } Entity *SelectionBuffer::OnSelectionClick(int x, int y) { Update(); size_t posInStream = (mPixelBox->getWidth() * y) * 4; posInStream += x * 4; BGRA color(0); memcpy((void *)&color, mBuffer+posInStream, 4); ColourValue cv; cv.setAsARGB(color); cv.a = 1.0f; const String &entName = mMaterialSwitchListener->getEntityName(cv); if(entName.empty()) return 0; else return mSceneMgr->getEntity(entName); } void SelectionBuffer::CreateRTTOverlays() { MaterialPtr baseWhite = MaterialManager::getSingleton().getDefaultSettings(); MaterialPtr selectionBufferTexture = baseWhite->clone("SelectionDebugMaterial"); TextureUnitState *textureUnit = selectionBufferTexture->getTechnique(0)->getPass(0)->createTextureUnitState(); textureUnit->setTextureName("SelectionPassTex"); OverlayManager *mgr = OverlayManager::getSingletonPtr(); mSelectionDebugOverlay = mgr->create("SelectionDebugOverlay"); OverlayContainer *panel = static_cast<OverlayContainer *>(mgr->createOverlayElement("Panel", "SelectionDebugPanel")); panel->setMetricsMode(GMM_PIXELS); panel->setPosition(10, 10); panel->setDimensions(400, 280); panel->setMaterialName("SelectionDebugMaterial"); mSelectionDebugOverlay->add2D(panel); mSelectionDebugOverlay->show(); } void SelectionBuffer::ShowOverlay(bool show) { if(show) mSelectionDebugOverlay->show(); else mSelectionDebugOverlay->hide(); }{CODE} !!{img fileId=2033"} Material Files !!!-{img fileId=2033} PlainColor.material {CODE(wrap="1", caption="PlainColor.material", colors="c++")}vertex_program PlainColor_VS cg { source PlainColor.cg entry_point main_plain_color_vp profiles vs_1_1 arbvp1 default_params { param_named_auto worldViewProj worldviewproj_matrix } } fragment_program PlainColor_PS cg { source PlainColor.cg entry_point main_plain_color_fp profiles ps_1_1 arbfp1 default_params { param_named inColor float4 1 1 1 1 } } material PlainColor { // Material has one technique technique { // This technique has one pass pass { // Make this pass use the vertex shader defined above vertex_program_ref PlainColor_VS { } // Make this pass use the pixel shader defined above fragment_program_ref PlainColor_PS { param_named_auto inColor custom 1 } } } }{CODE} !!!-{img fileId=2033} Plaincolor.cg {CODE(wrap="1", caption="Plaincolor.cg", colors="c++")}void main_plain_color_vp( // Vertex Inputs float4 position : POSITION, // Vertex position in model space float2 texCoord0 : TEXCOORD0, // Texture UV set 0 // Outputs out float4 oPosition : POSITION, // Transformed vertex position out float2 uv0 : TEXCOORD0, // UV0 // Model Level Inputs uniform float4x4 worldViewProj) { // Calculate output position oPosition = mul(worldViewProj, position); // Simply copy the input vertex UV to the output uv0 = texCoord0; } void main_plain_color_fp( // Pixel Inputs float2 uv0 : TEXCOORD0, // UV interpolated for current pixel // Outputs out float4 color : COLOR, // Output color we want to write uniform float4 inColor ) { color = inColor; }{CODE} !{img fileId=2036} Compatibility This code is known to work in Ogre 1.6 and Ogre 1.7.
Search by Tags
Search Wiki by Freetags
Latest Changes
FMOD SoundManager
HDRlib
Building Ogre V2 with CMake
Ogre 2.1 FAQ
Minimal Ogre Collision
Artifex Terra
OpenMB
Advanced Mogre Framework
MogreSocks
Critter AI
...more
Search
Find
Advanced
Search Help
Online Users
26 online users