Skip to main content

History: Selection Buffer

Preview of version: 21

%ogre17%%ogre16%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^24 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 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.

SelectionBuffer.h

Copy to clipboard
#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

MaterialSwitcher.h

Copy to clipboard
#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

SelectionRenderListener.h

Copy to clipboard
#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

MaterialSwitcher.cpp

Copy to clipboard
#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(); }

SelectionRenderListener.cpp

Copy to clipboard
#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); }

SelectionBuffer.cpp

Copy to clipboard
#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->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(); }

PlainColor.material

Copy to clipboard
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 } } } }

Plaincolor.cg

Copy to clipboard
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; }

Compatibility


This code is known to work in Ogre 1.6 and Ogre 1.7.

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:

Copy to clipboard
#include "SelectionBuffer.h"

Then initialization:

Copy to clipboard
// 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);

Then process every frame:

Copy to clipboard
// 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"); } } }

History

Information Version
Wed 21 of Mar, 2012 21:00 GMT-0000 brunobg Added line mRenderTexture->getViewport(0)->setShadowsEnabled(false); so code works with shadows 25
Tue 05 of Apr, 2011 18:52 GMT-0000 DragonM Add background color specification to floated table of contents div, which makes it opaque. Removed clear dynamic variable since the opaque div covers section header horizontal rules. 24
Tue 05 of Apr, 2011 18:11 GMT-0000 DragonM Add description section title. 23
Tue 05 of Apr, 2011 01:38 GMT-0000 DragonM Fix section indents, add icons, add download link to source archive, reorder sections, float table of contents and collapse source sections since they're less important with a download. 22
Fri 30 of Apr, 2010 18:50 GMT-0000 jacmoe 21
Fri 30 of Apr, 2010 18:44 GMT-0000 jacmoe 20
Fri 30 of Apr, 2010 18:43 GMT-0000 jacmoe 19
Fri 30 of Apr, 2010 18:42 GMT-0000 jacmoe 18
Fri 30 of Apr, 2010 18:42 GMT-0000 jacmoe 17
Fri 30 of Apr, 2010 18:41 GMT-0000 jacmoe 16
Fri 30 of Apr, 2010 18:41 GMT-0000 jacmoe 15
Fri 30 of Apr, 2010 18:40 GMT-0000 jacmoe 14
Fri 30 of Apr, 2010 18:35 GMT-0000 jacmoe 13
Fri 30 of Apr, 2010 18:33 GMT-0000 jacmoe 12
Fri 30 of Apr, 2010 18:32 GMT-0000 jacmoe 11
Fri 30 of Apr, 2010 18:27 GMT-0000 jacmoe 10
Fri 30 of Apr, 2010 18:26 GMT-0000 jacmoe 9
Fri 30 of Apr, 2010 18:26 GMT-0000 jacmoe 8
Fri 30 of Apr, 2010 18:25 GMT-0000 jacmoe 7
Fri 30 of Apr, 2010 18:24 GMT-0000 jacmoe 6
Fri 30 of Apr, 2010 18:23 GMT-0000 jacmoe 5
Fri 30 of Apr, 2010 18:21 GMT-0000 jacmoe 4
Fri 30 of Apr, 2010 18:19 GMT-0000 jacmoe 3
Fri 30 of Apr, 2010 18:18 GMT-0000 jacmoe 2
Fri 30 of Apr, 2010 18:15 GMT-0000 jacmoe 1