Object Picking

SomeFusion

18-01-2009 12:57:23

Hi,

i'm currently trying to implement object picking in my editor. I try to use ogrenewt for this task. This is the code I have currently.

col = on.TreeCollision(self.World, entity.getParentNode(), False)
bod = on.Body(self.World, col)


start = ray.getOrigin()
end = ray.getPoint(1000.0)

retNorm = og.Vector3(0.0, 0.0, 0.0)
#redId = None

val = on.CollisionRayCast(col, start, end, retNorm)
#val = on.CollisionRayCast(col, start, end, retNorm, retId) does not work because of a function missmatch
print val

print retNorm.x
print retNorm.y
print retNorm.z
del col
self.World.destroyAllBodies()

if retNorm.x != 0.0 or retNorm.y != 0.0 or retNorm.z != 0.0:
print "yes"
return True
else:
print "no"
return False


This code works sometime and sometime is doesn't. For smaller meshes it always doesn't work.

Here's a small video demonstrating the problem.
http://www.youtube.com/watch?v=3fM1YasBKdo&fmt=18

The white line you can barely see is a visual representation of the last ray thats used to do the check.
I turn on the bounding box visualization when a entity gets selected.

I post this here because maybe someone has a better idea how to do this in python in a fast way.

Thanks everyone
somefusion

dermont

19-01-2009 07:45:14

Sorry I don't know the answer to your particular problem, but for object picking could you not just use:

a) Ray
camera.getCameraToViewportRay(..) / RaySceneQuery
b) RubberBand
camera.getCameraToViewportBoxVolume(..) / PlaneBoundedVolumeListSceneQuery

bharling

19-01-2009 07:53:22

You could try converting this to python ( shouldn't be too hard ) but I guess it will probably run very slowly for objects with many polys.

http://www.ogre3d.org/wiki/index.php/Raycasting_to_the_polygon_level

OgreNewt should solve your problem really, though I never managed to get very far with it myself. I watched the video, its difficult to tell, but sometimes it looks like the viewport ray is not going in the right direction, could that be an issue?

SomeFusion

24-01-2009 15:08:32

Sorry for the late answer. I was out of town this week. Doing object picking in python is to slow for bigger objects and using ogres build in RaySceneQuery's are to inaccurate.
So I think using a physics engine the only way to do this.

bharling

29-01-2009 10:56:57

Another way is to use a selection buffer, something which sinbad suggests on the main forum. You just do a fullscreen render of your scene with objects using special materials colour coded for selection ( also no lighting ) sent to a hidden render target, then just get the colour value from the mouse pointer position in that image and match it up to find out what object was clicked (or at least what material). That way you will get pixel perfect object picking, downside is you need another render target ( but you could manage this manually, theres no need to update it unless the user clicks the mouse). You can manage the materials in a similar way to the Depth of field demo I did, ie: use renderTargetListener and material schemes.

SomeFusion

03-02-2009 21:48:37

Ah thanks for the tips. Seems like a very fast and convenient way to do it. I have started implementing the buffer today using your material example in the wiki. Here's the (very unfinished) code:


import ogre.renderer.OGRE as og

# class to handle material switching without having to modify scene materials individually
class MaterialSwitcher( og.MaterialManager.Listener ):
def __init__(self):
og.MaterialManager.Listener.__init__(self)
selectionMat = og.MaterialManager.getSingleton().getByName("BasicSelectionBuffer")
selectionMat.load()
self.selectionTechnique = selectionMat.getBestTechnique()

self.currentColor = og.ColourValue(0.0, 0.0, 0.0)
self.lastEntity = ""

# takes into account that one Entity can have multiple SubEntities
def handleSchemeNotFound(self, index, name, material, lod, rend):
if self.lastEntity == rend.getParent().getName():
self.selectionTechnique.setDiffuse(self.currentColor)
self.selectionTechnique.setAmbient(self.currentColor)
self.selectionTechnique.setSpecular(self.currentColor)

print str(self.currentColor.r) + " " + str(self.currentColor.g) + " " + str(self.currentColor.b) + " " + self.lastEntity
return self.selectionTechnique
else:
self.incrementColor()
self.selectionTechnique.setDiffuse(self.currentColor)
self.selectionTechnique.setAmbient(self.currentColor)
self.selectionTechnique.setSpecular(self.currentColor)

self.lastEntity = rend.getParent().getName()
print str(self.currentColor.r) + " " + str(self.currentColor.g) + " " + str(self.currentColor.b) + " " + self.lastEntity
return self.selectionTechnique


def incrementColor(self):
self.currentColor += og.ColourValue(0.1, 0.2, 0.3)

def reset(self):
self.currentColor = og.ColourValue(0.0, 0.0, 0.0)

# We need this attached to the depth target, otherwise we get problems with the compositor
# MaterialManager.Listener should NOT be running all the time - rather only when we're
# specifically rendering the target that needs it
class SelectionRenderListener(og.RenderTargetListener):
def __init__(self, materialListener):
og.RenderTargetListener.__init__(self)
self.materialListener = materialListener

def preRenderTargetUpdate(self, evt):
og.MaterialManager.getSingleton().addListener( self.materialListener )

def postRenderTargetUpdate(self, evt):
og.MaterialManager.getSingleton().removeListener( self.materialListener )


class SelectionBuffer():
def __init__(self, sceneManager, ogreRenderWindow):
self.sceneMgr = sceneManager
self.camera = sceneManager.getCamera("MainCam")

self.ogreRenderWindow = ogreRenderWindow
self.viewport = ogreRenderWindow.getViewport(0)

# This is the material listener - Note: it is controlled by a seperate
# RenderTargetListener, not applied globally to all targets
self.materialSwitchListener = MaterialSwitcher()

self.selectionTargetListener = SelectionRenderListener( self.materialSwitchListener )

texture = og.TextureManager.getSingleton().createManual("SelectionPassTex",
og.ResourceGroupManager.DEFAULT_RESOURCE_GROUP_NAME,
og.TEX_TYPE_2D,
self.viewport.getActualWidth(),
self.viewport.getActualHeight(),
0, og.PixelFormat.PF_R8G8B8, og.TU_RENDERTARGET)

self.renderTexture = texture.getBuffer().getRenderTarget()
self.renderTexture.setAutoUpdated(False)
self.renderTexture.setPriority(0)
self.renderTexture.addViewport( self.camera )
self.renderTexture.getViewport(0).setOverlaysEnabled(False)
self.renderTexture.getViewport(0).setClearEveryFrame( True )
self.renderTexture.addListener( self.selectionTargetListener )
self.renderTexture.getViewport(0).setMaterialScheme("aa")

self.createRTTOverlays()

def update(self):
self.renderTexture.update()
self.materialSwitchListener.reset()

def createRTTOverlays(self):
baseWhite = og.MaterialManager.getSingletonPtr().getByName("BasicSelectionBuffer")
SelectionBufferTexture = baseWhite.clone("SelectionDebugMaterial")
textureUnit = SelectionBufferTexture.getTechnique(0).getPass(0).createTextureUnitState()

textureUnit.setTextureName("SelectionPassTex")


overlayManager = og.OverlayManager.getSingleton()
# Create an overlay
self.mDebugOverlay = overlayManager.create("OverlayName")

# Create a panel
panel = overlayManager.createOverlayElement("Panel", "PanelName0")
panel.setMetricsMode(og.GMM_PIXELS)
panel.setPosition(10, 10)
#panel.setDimensions(320, 240)
panel.setDimensions(400, 280)
panel.setMaterialName("SelectionDebugMaterial")
self.mDebugOverlay.add2D(panel)

self.mDebugOverlay.show()


And the material
material BasicSelectionBuffer
{
technique
{
pass
{
lighting off

ambient 1.0 1.0 1.0 1.0
diffuse 1.0 1.0 1.0 1.0
emissive 1.0 1.0 1.0 1.0
}
}
}


Now the problem is that the models are rendered but they are always plain white.
When I add a texture_pass to the BasicSelectionBuffer material, its rendered with the texture applied. So the technique itself is fine.

http://img55.imageshack.us/img55/603/rttproblemfr0.jpg

Thanks!

bharling

03-02-2009 22:51:32

cool, nice to see you're trying it out!

as for the colouring problem you're experiencing,

self.selectionTechnique.setDiffuse(self.currentColor)
self.selectionTechnique.setAmbient(self.currentColor)
self.selectionTechnique.setSpecular(self.currentColor)

print str(self.currentColor.r) + " " + str(self.currentColor.g) + " " + str(self.currentColor.b) + " " + self.lastEntity
return self.selectionTechnique


I think you might need to return a unique technique in this instance, I believe self.selectionTechnique will always refer to the same technique so all the materials will have the same settings as the last time its been changed. You could queue up a batch of techniques and apply them based on the renderable. Alternatively -

self.renderTexture.addListener( self.selectionTargetListener )
self.renderTexture.getViewport(0).setMaterialScheme("Selection_Colours")


You can deal with it in your materials directly by implementing the 'Selection_Colours' or whatever you call it, scheme in the material script itself.

EDIT: to clarify a bit:

The materialManager.Listener would apply a blank material to all objects it catches with the 'handleSchemeNotFound" function. Whereas objects you want to be selectable would have this added to their material scripts ( or at runtime perhaps ).

material MySelectableHouse
{
technique
{
pass
{
texture_unit blah
{
texture "wall.jpg"
}
}
}
technique
{
scheme Selection_Colours
pass
{
lighting off
diffuse 1.0 0.0 0.0 1.0
}
}
}


This should mean the house will always be rendered pure red when it appears in the selection buffer. ( note my syntax might be a bit off, just wrote this now ).

SomeFusion

04-02-2009 21:47:03

Hi,

it works when I return a unique technique for every request :)

I have the result now in a PixelBox, but how do I utilize it in Python?
When I try it like this:
pixelBuffer = self.texture.getBuffer()
pBox = pixelBuffer.getCurrentLock()

storageclass = ctypes.c_uint8 * (self.renderTexture.getWidth()*self.renderTexture.getHeight()*3)
cbuffer=storageclass.from_address(pBox.data)


pixelBuffer.unlock()


My app and python crashed. I have also tried pBox.getData() but this crashes too.

Thanks again!

andy

06-02-2009 00:22:09

You can simply set the data variable to the address of a buffer you have created with ctypes.. Something like:
## create our buffer (ensure it stays in scope while being used)
storageclass = ctypes.c_uint8 * (nBuffSize) # allocate a buffer class
buff=storageclass()
## create pixel box with an empty buffer
Pb = ogre.PixelBox(width, height,depth, format )
## Set the buffer to point to our one...
Pb.data = ctypes.addressof(buff)

Regards
Andy

SomeFusion

08-02-2009 18:23:01

Hey guys,

thanks for helping me out here. I have posted the code of my SelectionBuffer to the wiki. I think this may be usefull to others aswell.
I'm not good at documenting, if something is unclear just ask :)

http://wiki.python-ogre.org/index.php/C ... ion_Buffer

Here's a image of it in action:
http://img223.imageshack.us/my.php?imag ... 096is5.jpg