Commit c1eac93b authored by Ken Martin's avatar Ken Martin
Browse files

Improve picking support in OpenGL2

Point association picking was not implemented this patch
adds that in. Also cell picking did not work when the input
contained quads or higher vertex count polygons.  To address
these issues the picking process needs to give the mapper an
opporunity to map the index from the frame buffer back into
a point id or cell id. Added amvirtual method in prop to
do that and an implementation in OpenGLActor and
OpenGLPolyDataMapper to properly map gl_PrimativeId in
the correct point or cell id.

Point picking was not tested in VTK so I added a test for
that as well.

Change-Id: If276e80ab3966646dd9f6f95fbc7734c006f000c
parent 28e05a3a
......@@ -38,6 +38,7 @@ vtk_add_test_cxx(${vtk-module}CxxTests tests
TestOrderedTriangulator.cxx
TestOpacity.cxx
TestOSConeCxx.cxx
TestPointSelection.cxx,NO_VALID
TestPolygonSelection.cxx
TestResetCameraVerticalAspectRatio.cxx
TestResetCameraVerticalAspectRatioParallel.cxx
......
/*=========================================================================
Program: Visualization Toolkit
Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
All rights reserved.
See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notice for more information.
=========================================================================*/
#include "vtkTestUtilities.h"
#include "vtkRegressionTestImage.h"
#include "vtkActor.h"
#include "vtkCamera.h"
#include "vtkCommand.h"
#include "vtkHardwareSelector.h"
#include "vtkIdTypeArray.h"
#include "vtkInformation.h"
#include "vtkInteractorStyleRubberBandPick.h"
#include "vtkNew.h"
#include "vtkPolyDataMapper.h"
#include "vtkProp3DCollection.h"
#include "vtkRenderedAreaPicker.h"
#include "vtkRenderer.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderWindow.h"
#include "vtkSelection.h"
#include "vtkSelectionNode.h"
#include "vtkSphereSource.h"
class PointPickCommand : public vtkCommand
{
protected:
vtkNew<vtkIdTypeArray> PointIds;
vtkRenderer *Renderer;
vtkAreaPicker *Picker;
vtkPolyDataMapper *Mapper;
public:
static PointPickCommand * New() {return new PointPickCommand;}
vtkTypeMacro(PointPickCommand, vtkCommand);
PointPickCommand()
{
}
virtual ~PointPickCommand()
{
}
void SetPointIds(vtkSelection *selection)
{
// Find selection node that we're interested in:
const vtkIdType numNodes = selection->GetNumberOfNodes();
for (vtkIdType nodeId = 0; nodeId < numNodes; ++nodeId)
{
vtkSelectionNode *node = selection->GetNode(nodeId);
// Check if the mapper is this instance of MoleculeMapper
vtkActor *selActor = vtkActor::SafeDownCast(
node->GetProperties()->Get(vtkSelectionNode::PROP()));
if (selActor && (selActor->GetMapper() == this->Mapper))
{
// Separate the selection ids into atoms and bonds
vtkIdTypeArray *selIds = vtkIdTypeArray::SafeDownCast(
node->GetSelectionList());
if (selIds)
{
vtkIdType numIds = selIds->GetNumberOfTuples();
for (vtkIdType i = 0; i < numIds; ++i)
{
vtkIdType curId = selIds->GetValue(i);
this->PointIds->InsertNextValue(curId);
}
}
}
}
}
vtkIdTypeArray *GetPointIds()
{
return this->PointIds.GetPointer();
}
void SetMapper(vtkPolyDataMapper *m)
{
this->Mapper = m;
}
void SetRenderer(vtkRenderer *r)
{
this->Renderer = r;
}
void SetPicker(vtkAreaPicker *p)
{
this->Picker = p;
}
virtual void Execute(vtkObject *, unsigned long, void *)
{
vtkProp3DCollection *props = this->Picker->GetProp3Ds();
if (props->GetNumberOfItems() != 0)
{
// If anything was picked during the fast area pick, do a more detailed
// pick.
vtkNew<vtkHardwareSelector> selector;
selector->SetFieldAssociation(vtkDataObject::FIELD_ASSOCIATION_POINTS);
selector->SetRenderer(this->Renderer);
selector->SetArea(
static_cast<unsigned int>(this->Renderer->GetPickX1()),
static_cast<unsigned int>(this->Renderer->GetPickY1()),
static_cast<unsigned int>(this->Renderer->GetPickX2()),
static_cast<unsigned int>(this->Renderer->GetPickY2()));
// Make the actual pick and pass the result to the convenience function
// defined earlier
vtkSelection *result = selector->Select();
this->SetPointIds(result);
this->DumpPointSelection();
result->Delete();
}
}
// Convenience function to print out the atom and bond ids that belong to
// molMap and are contained in sel
void DumpPointSelection()
{
// Print selection
cerr << "\n### Selection ###\n";
cerr << "Points: ";
for (vtkIdType i = 0; i < this->PointIds->GetNumberOfTuples(); i++)
{
cerr << this->PointIds->GetValue(i) << " ";
}
cerr << endl;
}
};
int TestPointSelection(int argc, char *argv[])
{
// create a line and a mesh
vtkNew<vtkSphereSource> sphere;
// Set up render engine
vtkNew<vtkPolyDataMapper> sphereMapper;
sphereMapper->SetInputConnection(sphere->GetOutputPort());
vtkNew<vtkActor> actor;
actor->SetMapper(sphereMapper.GetPointer());
vtkNew<vtkRenderer> ren;
ren->AddActor(actor.GetPointer());
vtkNew<vtkRenderWindow> win;
win->SetMultiSamples(0);
win->AddRenderer(ren.GetPointer());
vtkNew<vtkRenderWindowInteractor> iren;
iren->SetRenderWindow(win.GetPointer());
ren->SetBackground(0.0,0.0,0.0);
win->SetSize(450,450);
win->Render();
ren->GetActiveCamera()->Zoom(1.2);
// Setup picker
vtkNew<vtkInteractorStyleRubberBandPick> pickerInt;
iren->SetInteractorStyle(pickerInt.GetPointer());
vtkNew<vtkRenderedAreaPicker> picker;
iren->SetPicker(picker.GetPointer());
// We'll follow up the cheap RenderedAreaPick with a detailed selection
// to obtain the atoms and bonds.
vtkNew<PointPickCommand> com;
com->SetRenderer(ren.GetPointer());
com->SetPicker(picker.GetPointer());
com->SetMapper(sphereMapper.GetPointer());
picker->AddObserver(vtkCommand::EndPickEvent, com.GetPointer());
// Make pick -- lower left quarter of renderer
win->Render();
picker->AreaPick(0, 0, 225, 225, ren.GetPointer());
win->Render();
// Interact if desired
int retVal = vtkRegressionTestImage(win.GetPointer());
if ( retVal == vtkRegressionTester::DO_INTERACTOR)
{
iren->Start();
}
// Verify pick
if (com->GetPointIds()->GetNumberOfTuples() < 7 ||
com->GetPointIds()->GetValue(0) != 0 ||
com->GetPointIds()->GetValue(1) != 26 ||
com->GetPointIds()->GetValue(2) != 27 ||
com->GetPointIds()->GetValue(3) != 32 ||
com->GetPointIds()->GetValue(4) != 33 ||
com->GetPointIds()->GetValue(5) != 38 ||
com->GetPointIds()->GetValue(6) != 39
)
{
cerr << "Incorrect atoms/bonds picked! (if any picks were performed inter"
"actively this could be ignored).\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
......@@ -133,7 +133,7 @@ public:
vtkIdType cc=0;
for (idIter = id_values.begin(); idIter != id_values.end(); ++idIter, ++cc)
{
ptr[cc] = *idIter;
ptr[cc] = key.Prop->GetConvertedPickValue(*idIter, fieldassociation);
}
child->SetSelectionList(ids);
ids->FastDelete();
......
......@@ -145,6 +145,14 @@ public:
// \pre keys_can_be_null: requiredKeys==0 || requiredKeys!=0
virtual bool HasKeys(vtkInformation *requiredKeys);
// Description:
// Props may provide a mapping from picked value to actual value
// This is useful for hardware based pickers where
// there is a mapping between the color in the buffer
// and the actual pick value
virtual vtkIdType GetConvertedPickValue(vtkIdType idIn,
int vtkNotUsed(fieldassociation)) { return idIn; }
//BTX
// Description:
// WARNING: INTERNAL METHOD - NOT INTENDED FOR GENERAL USE
......
......@@ -18,6 +18,7 @@
#include "vtkMatrix3x3.h"
#include "vtkMatrix4x4.h"
#include "vtkObjectFactory.h"
#include "vtkOpenGLPolyDataMapper.h"
#include "vtkOpenGLRenderer.h"
#include "vtkProperty.h"
#include "vtkOpenGLError.h"
......@@ -124,3 +125,13 @@ void vtkOpenGLActor::GetKeyMatrices(vtkMatrix4x4 *&mcwc, vtkMatrix3x3 *&normMat)
mcwc = this->MCWCMatrix;
normMat = this->NormalMatrix;
}
vtkIdType vtkOpenGLActor::GetConvertedPickValue(vtkIdType idIn, int fieldassociation)
{
vtkOpenGLPolyDataMapper *pdm = vtkOpenGLPolyDataMapper::SafeDownCast(this->GetMapper());
if (pdm)
{
return pdm->GetConvertedPickValue(idIn, fieldassociation,this);
}
return idIn;
}
......@@ -40,6 +40,13 @@ public:
void GetKeyMatrices(vtkMatrix4x4 *&WCVCMatrix, vtkMatrix3x3 *&normalMatrix);
// Description:
// Props may provide a mapping from picked value to actual value
// This is useful for hardware based pickers where
// there is a mapping between the color in the buffer
// and the actual pick value
virtual vtkIdType GetConvertedPickValue(vtkIdType idIn, int fieldassociation);
protected:
vtkOpenGLActor();
~vtkOpenGLActor();
......
......@@ -1002,10 +1002,25 @@ void vtkOpenGLPolyDataMapper::SetPropertyShaderParameters(vtkgl::CellBO &cellBO,
//-----------------------------------------------------------------------------
void vtkOpenGLPolyDataMapper::RenderPieceStart(vtkRenderer* ren, vtkActor *actor)
{
// Set the PointSize and LineWidget
#if GL_ES_VERSION_2_0 != 1
glPointSize(actor->GetProperty()->GetPointSize()); // not on ES2
#endif
glLineWidth(actor->GetProperty()->GetLineWidth()); // supported by all OpenGL versions
vtkHardwareSelector* selector = ren->GetSelector();
if (selector && this->PopulateSelectionSettings)
{
selector->BeginRenderProp();
// render points for point picking in a special way
if (selector->GetFieldAssociation() == vtkDataObject::FIELD_ASSOCIATION_POINTS &&
selector->GetCurrentPass() > vtkHardwareSelector::ACTOR_PASS)
{
glPointSize(4.0); //make verts large enough to be sure to overlap cell
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(0,2.0); // supported on ES2/3/etc
glDepthMask(GL_FALSE); //prevent verts from interfering with each other
}
if (selector->GetCurrentPass() == vtkHardwareSelector::COMPOSITE_INDEX_PASS)
{
selector->RenderCompositeIndex(1);
......@@ -1021,16 +1036,17 @@ void vtkOpenGLPolyDataMapper::RenderPieceStart(vtkRenderer* ren, vtkActor *actor
this->TimeToDraw = 0.0;
this->pickingAttributeIDOffset = 0;
bool picking = (ren->GetIsPicking() || selector != NULL);
// Update the OpenGL if needed.
if (this->OpenGLUpdateTime < this->GetMTime() ||
this->OpenGLUpdateTime < actor->GetMTime() ||
this->OpenGLUpdateTime < this->CurrentInput->GetMTime() )
this->OpenGLUpdateTime < this->CurrentInput->GetMTime() ||
this->LastSelectionState || picking)
{
this->UpdateVBO(ren, actor);
this->OpenGLUpdateTime.Modified();
}
// If we are coloring by texture, then load the texture map.
// Use Map as indicator, because texture hangs around.
if (this->InternalColorTexture)
......@@ -1043,12 +1059,6 @@ void vtkOpenGLPolyDataMapper::RenderPieceStart(vtkRenderer* ren, vtkActor *actor
this->LastBoundBO = NULL;
// Set the PointSize and LineWidget
#if GL_ES_VERSION_2_0 != 1
glPointSize(actor->GetProperty()->GetPointSize()); // not on ES2
#endif
glLineWidth(actor->GetProperty()->GetLineWidth()); // supported by all OpenGL versions
if ( this->GetResolveCoincidentTopology() )
{
glEnable(GL_POLYGON_OFFSET_FILL);
......@@ -1067,6 +1077,7 @@ void vtkOpenGLPolyDataMapper::RenderPieceStart(vtkRenderer* ren, vtkActor *actor
glPolygonOffset(f,u); // supported on ES2/3/etc
}
}
}
//-----------------------------------------------------------------------------
......@@ -1089,12 +1100,24 @@ void vtkOpenGLPolyDataMapper::RenderPieceDraw(vtkRenderer* ren, vtkActor *actor)
this->pickingAttributeIDOffset += (int)this->Points.indexCount;
}
int representation = actor->GetProperty()->GetRepresentation();
// render points for point picking in a special way
// all cell types should be rendered as points
vtkHardwareSelector* selector = ren->GetSelector();
if (selector && this->PopulateSelectionSettings &&
selector->GetFieldAssociation() == vtkDataObject::FIELD_ASSOCIATION_POINTS &&
selector->GetCurrentPass() > vtkHardwareSelector::ACTOR_PASS)
{
representation = VTK_POINTS;
}
// draw lines
if (this->Lines.indexCount)
{
this->UpdateShader(this->Lines, ren, actor);
this->Lines.ibo.Bind();
if (actor->GetProperty()->GetRepresentation() == VTK_POINTS)
if (representation == VTK_POINTS)
{
glDrawRangeElements(GL_POINTS, 0,
static_cast<GLuint>(layout.VertexCount - 1),
......@@ -1120,8 +1143,8 @@ void vtkOpenGLPolyDataMapper::RenderPieceDraw(vtkRenderer* ren, vtkActor *actor)
// First we do the triangles, update the shader, set uniforms, etc.
this->UpdateShader(this->Tris, ren, actor);
this->Tris.ibo.Bind();
GLenum mode = (actor->GetProperty()->GetRepresentation() == VTK_POINTS) ? GL_POINTS :
(actor->GetProperty()->GetRepresentation() == VTK_WIREFRAME) ? GL_LINES : GL_TRIANGLES;
GLenum mode = (representation == VTK_POINTS) ? GL_POINTS :
(representation == VTK_WIREFRAME) ? GL_LINES : GL_TRIANGLES;
glDrawRangeElements(mode, 0,
static_cast<GLuint>(layout.VertexCount - 1),
static_cast<GLsizei>(this->Tris.indexCount),
......@@ -1137,7 +1160,7 @@ void vtkOpenGLPolyDataMapper::RenderPieceDraw(vtkRenderer* ren, vtkActor *actor)
// Use the tris shader program/VAO, but triStrips ibo.
this->UpdateShader(this->TriStrips, ren, actor);
this->TriStrips.ibo.Bind();
if (actor->GetProperty()->GetRepresentation() == VTK_POINTS)
if (representation == VTK_POINTS)
{
glDrawRangeElements(GL_POINTS, 0,
static_cast<GLuint>(layout.VertexCount - 1),
......@@ -1145,7 +1168,7 @@ void vtkOpenGLPolyDataMapper::RenderPieceDraw(vtkRenderer* ren, vtkActor *actor)
GL_UNSIGNED_INT,
reinterpret_cast<const GLvoid *>(NULL));
}
if (actor->GetProperty()->GetRepresentation() == VTK_WIREFRAME)
if (representation == VTK_WIREFRAME)
{
glMultiDrawElements(GL_LINE_STRIP,
(GLsizei *)(&this->TriStrips.elementsArray[0]),
......@@ -1153,7 +1176,7 @@ void vtkOpenGLPolyDataMapper::RenderPieceDraw(vtkRenderer* ren, vtkActor *actor)
reinterpret_cast<const GLvoid **>(&(this->TriStrips.offsetArray[0])),
(GLsizei)this->TriStrips.offsetArray.size());
}
if (actor->GetProperty()->GetRepresentation() == VTK_SURFACE)
if (representation == VTK_SURFACE)
{
glMultiDrawElements(GL_TRIANGLE_STRIP,
(GLsizei *)(&this->TriStrips.elementsArray[0]),
......@@ -1172,6 +1195,13 @@ void vtkOpenGLPolyDataMapper::RenderPieceFinish(vtkRenderer* ren, vtkActor *vtkN
vtkHardwareSelector* selector = ren->GetSelector();
if (selector && this->PopulateSelectionSettings)
{
// render points for point picking in a special way
if (selector->GetFieldAssociation() == vtkDataObject::FIELD_ASSOCIATION_POINTS &&
selector->GetCurrentPass() > vtkHardwareSelector::ACTOR_PASS)
{
glDepthMask(GL_TRUE);
glDisable(GL_POLYGON_OFFSET_FILL);
}
selector->EndRenderProp();
}
......@@ -1324,7 +1354,7 @@ void vtkOpenGLPolyDataMapper::ComputeBounds()
}
//-------------------------------------------------------------------------
void vtkOpenGLPolyDataMapper::UpdateVBO(vtkRenderer *vtkNotUsed(ren), vtkActor *act)
void vtkOpenGLPolyDataMapper::UpdateVBO(vtkRenderer *ren, vtkActor *act)
{
vtkPolyData *poly = this->CurrentInput;
......@@ -1427,7 +1457,17 @@ void vtkOpenGLPolyDataMapper::UpdateVBO(vtkRenderer *vtkNotUsed(ren), vtkActor *
this->Points.indexCount = CreatePointIndexBuffer(prims[0],
this->Points.ibo);
if (act->GetProperty()->GetRepresentation() == VTK_POINTS)
int representation = act->GetProperty()->GetRepresentation();
vtkHardwareSelector* selector = ren->GetSelector();
if (selector && this->PopulateSelectionSettings &&
selector->GetFieldAssociation() == vtkDataObject::FIELD_ASSOCIATION_POINTS &&
selector->GetCurrentPass() > vtkHardwareSelector::ACTOR_PASS)
{
representation = VTK_POINTS;
}
if (representation == VTK_POINTS)
{
this->Lines.indexCount = CreatePointIndexBuffer(prims[1],
this->Lines.ibo);
......@@ -1444,7 +1484,7 @@ void vtkOpenGLPolyDataMapper::UpdateVBO(vtkRenderer *vtkNotUsed(ren), vtkActor *
this->Lines.offsetArray,
this->Lines.elementsArray, false);
if (act->GetProperty()->GetRepresentation() == VTK_WIREFRAME)
if (representation == VTK_WIREFRAME)
{
vtkDataArray *ef = poly->GetPointData()->GetAttribute(
vtkDataSetAttributes::EDGEFLAG);
......@@ -1570,6 +1610,148 @@ bool vtkOpenGLPolyDataMapper::GetIsOpaque()
return this->Superclass::GetIsOpaque();
}
vtkIdType vtkOpenGLPolyDataMapper::GetConvertedPickValue(vtkIdType idIn, int fieldassociation, vtkActor *act)
{
vtkPolyData *poly = this->CurrentInput;
vtkCellArray *prims[4];
prims[0] = poly->GetVerts();
prims[1] = poly->GetLines();
prims[2] = poly->GetPolys();
prims[3] = poly->GetStrips();
vtkIdType* indices(NULL);
vtkIdType npts(0);
vtkIdType localId = idIn;
// handle cell picking
if (fieldassociation == vtkDataObject::FIELD_ASSOCIATION_CELLS)
{
// for points the cell is the cell, easy peasy
if (static_cast<size_t>(localId) < this->Points.indexCount)
{
return localId;
}
localId -= this->Points.indexCount;
vtkIdType offset = 0; // adjustment between OpenGL cells and VTK cells
int representation = act->GetProperty()->GetRepresentation();
// for lines the cell has to be computed because we do not
// know how many line segments are in the polyline
if (this->Lines.indexCount > 0)
{
// compute the location in the cell array
vtkIdType cellCount = 0;
for (prims[1]->InitTraversal(); prims[1]->GetNextCell(npts, indices); )
{
vtkIdType numCells = (representation == VTK_POINTS) ? npts : (npts - 1);
if (localId < cellCount + numCells)
{
return localId + offset;
}
offset = offset + numCells - 1;
cellCount += numCells;
}
localId -= (this->Lines.indexCount/2);
}
// for polys the cell has to be computed because we do not
// know how many triangles are in the poly
if (this->Tris.indexCount > 0)
{
// compute the location in the cell array
vtkIdType cellCount = 0;
for (prims[2]->InitTraversal(); prims[2]->GetNextCell(npts, indices); )
{
vtkIdType numCells = (representation == VTK_POINTS) ? npts :
(representation == VTK_WIREFRAME) ? npts : (npts - 2);
if (localId < cellCount + numCells)
{
return localId + offset;
}
offset = offset + numCells - 1;
cellCount += numCells;
}
localId -= (this->Tris.indexCount/3);
}
// for strips the cell maps exactly, easy peasy
if (static_cast<size_t>(localId) < this->TriStrips.indexCount)
{
return localId;
}
return 0;
}
// if we got here, then it is point based picking
// is it a point?
if (static_cast<size_t>(localId) < this->Points.indexCount)
{
prims[0]->GetCell(localId,npts,indices);
return indices[0];
}
localId -= this->Points.indexCount;
// when picking in point mode, we render all primitives as
// points. The graphics hardware tells us what point was
// picked. e.g. the 11th point. We have to convert that into
// a point ID. This can be done by traversing the cell
// arrays to find the 11th point rendered. But that operation
// can be expensive for the more complex cell arrays.
// You could speed this up significantly by building a
// monotonically increasing array of indexes that are
// not points in the cells array. Then a binary search
// would let you index quickly into the right array location.
// is it a line?
if (static_cast<size_t>(localId) < this->Lines.indexCount)
{
// compute the location in the cell array
vtkIdType pointCount = 0;
for (prims[1]->InitTraversal(); prims[1]->GetNextCell(npts, indices); )
{
if (localId < pointCount + npts)
{
return indices[localId - pointCount];
}
pointCount += npts;
}
}
localId -= this->Lines.indexCount;
// is it a poly
if (static_cast<size_t>(localId) < this->Tris.indexCount)
{
// compute the location in the cell array
vtkIdType pointCount = 0;
for (prims[2]->InitTraversal(); prims[2]->GetNextCell(npts, indices); )
{
if (localId < pointCount + npts)
{
return indices[localId - pointCount];
}
pointCount += npts;
}
}
localId -= this->Tris.indexCount;
// is it a strip?
if (static_cast<size_t>(localId) < this->TriStrips.indexCount)
{
// compute the location in the cell array
vtkIdType pointCount = 0;
for (prims[3]->InitTraversal(); prims[3]->GetNextCell(npts, indices); )
{
if (localId < pointCount + npts)