"Since we desperately needed a means for picking Lines
and Points I implemented (hopefully!) proper geometrical tests
for the PolytopeIntersector.
First of all I implemented a new "GenericPrimiteFunctor"
which is basically an extended copy TriangleFunctor which also
handles Points, Lines and Quads through suitable overloads of
operator(). I would have liked to call it "PrimitiveFunctor"
but that name was already used...
I used a template method to remove redundancy in the
drawElements method overloads. If you know of platforms where
this will not work I can change it to the style used
in TriangleFunctor.
In PolytopeIntersector.cpp I implemented a
"PolytopePrimitiveIntersector" which provides the needed
overloads for Points, Lines, Triangles and Quads to
the GenericPrimitiveFunctor. This is then used in the
intersect method of PolytopeIntersector.
Implementation summary:
- Points: Check distance to all planes
- Lines: Check distance of both ends against each plane.
If both are outside -> line is out
If both are in -> continue checking
One is in, one is out -> compute intersection point (candidate)
Then check all candidates against all other polytope
planes. The remaining candidates are the proper
intersection points of the line with the polytope.
- Triangles: Perform Line-Checks for all edges of the
triangle as above. If there is an proper intersection
-> done.
In the case where there are more than 2 polytope
plane to check against we have to check for the case
where the triangle encloses the polytope.
In that case the intersection lines of the polytope
planes are computed and checked against the triangle.
- Quads: handled as two triangles.
This is implementation is certainly not the fastest.
There are certainly ways and strategies to improve it.
I also enabled the code for PolytopeIntersector
in osgkeyboardmouse and added keybindings to
switch the type of intersector ('p') and the picking
coordinate system ('c') on the fly. Since the
PolytopeIntersector does not have a canonical
ordering for its intersections (as opposed to
the LineSegementIntersector) I chaged the
implementation to toggle all hit geometries.
I tested the functionality with osgkeyboardmouse
and several models and it seems to work for
polygonal models. Special nodes such as billboards
do not work.
The next thing on my todo-list is to implement
a an improved Intersection-Structure for the
PolytopeIntersector. We need to know
which primitives where hit (and where).
"
438 lines
14 KiB
C++
438 lines
14 KiB
C++
// C++ source file - (C) 2003 Robert Osfield, released under the OSGPL.
|
|
//
|
|
// Simple example of use of osgViewer::GraphicsWindow + SimpleViewer
|
|
// example that provides the user with control over view position with basic picking.
|
|
|
|
#include <osg/Timer>
|
|
#include <osg/io_utils>
|
|
#include <osg/observer_ptr>
|
|
|
|
#include <osgUtil/IntersectionVisitor>
|
|
#include <osgUtil/PolytopeIntersector>
|
|
#include <osgUtil/LineSegmentIntersector>
|
|
|
|
#include <osgDB/ReadFile>
|
|
#include <osgDB/WriteFile>
|
|
|
|
#include <osgGA/TrackballManipulator>
|
|
#include <osgGA/StateSetManipulator>
|
|
|
|
#include <osgViewer/SimpleViewer>
|
|
#include <osgViewer/GraphicsWindow>
|
|
|
|
#include <osgFX/Scribe>
|
|
|
|
#include <iostream>
|
|
|
|
class CreateModelToSaveVisitor : public osg::NodeVisitor
|
|
{
|
|
public:
|
|
|
|
CreateModelToSaveVisitor():
|
|
osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
|
|
{
|
|
_group = new osg::Group;
|
|
_addToModel = false;
|
|
}
|
|
|
|
virtual void apply(osg::Node& node)
|
|
{
|
|
osgFX::Scribe* scribe = dynamic_cast<osgFX::Scribe*>(&node);
|
|
if (scribe)
|
|
{
|
|
for(unsigned int i=0; i<scribe->getNumChildren(); ++i)
|
|
{
|
|
_group->addChild(scribe->getChild(i));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
traverse(node);
|
|
}
|
|
}
|
|
|
|
osg::ref_ptr<osg::Group> _group;
|
|
bool _addToModel;
|
|
};
|
|
|
|
class DeleteSelectedNodesVisitor : public osg::NodeVisitor
|
|
{
|
|
public:
|
|
|
|
DeleteSelectedNodesVisitor():
|
|
osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
|
|
{
|
|
}
|
|
|
|
virtual void apply(osg::Node& node)
|
|
{
|
|
osgFX::Scribe* scribe = dynamic_cast<osgFX::Scribe*>(&node);
|
|
if (scribe)
|
|
{
|
|
_selectedNodes.push_back(scribe);
|
|
}
|
|
else
|
|
{
|
|
traverse(node);
|
|
}
|
|
}
|
|
|
|
void pruneSelectedNodes()
|
|
{
|
|
for(SelectedNodes::iterator itr = _selectedNodes.begin();
|
|
itr != _selectedNodes.end();
|
|
++itr)
|
|
{
|
|
osg::Node* node = itr->get();
|
|
osg::Node::ParentList parents = node->getParents();
|
|
for(osg::Node::ParentList::iterator pitr = parents.begin();
|
|
pitr != parents.end();
|
|
++pitr)
|
|
{
|
|
osg::Group* parent = *pitr;
|
|
parent->removeChild(node);
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef std::vector< osg::ref_ptr<osgFX::Scribe> > SelectedNodes;
|
|
SelectedNodes _selectedNodes;
|
|
|
|
};
|
|
|
|
class ExitHandler : public osgGA::GUIEventHandler
|
|
{
|
|
public:
|
|
|
|
ExitHandler():
|
|
_done(false) {}
|
|
|
|
bool done() const { return _done; }
|
|
|
|
bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter&)
|
|
{
|
|
switch(ea.getEventType())
|
|
{
|
|
case(osgGA::GUIEventAdapter::KEYUP):
|
|
{
|
|
if (ea.getKey()==osgGA::GUIEventAdapter::KEY_Escape)
|
|
{
|
|
_done = true;
|
|
}
|
|
return false;
|
|
}
|
|
case(osgGA::GUIEventAdapter::CLOSE_WINDOW):
|
|
case(osgGA::GUIEventAdapter::QUIT_APPLICATION):
|
|
{
|
|
_done = true;
|
|
}
|
|
default: break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool _done;
|
|
};
|
|
|
|
|
|
|
|
// class to handle events with a pick
|
|
class PickHandler : public osgGA::GUIEventHandler
|
|
{
|
|
public:
|
|
|
|
PickHandler():
|
|
_mx(0.0),_my(0.0),
|
|
_usePolytopeIntersector(false),
|
|
_useWindowCoordinates(false) {}
|
|
|
|
~PickHandler() {}
|
|
|
|
bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
|
|
{
|
|
osgViewer::SimpleViewer* viewer = dynamic_cast<osgViewer::SimpleViewer*>(&aa);
|
|
if (!viewer) return false;
|
|
|
|
switch(ea.getEventType())
|
|
{
|
|
case(osgGA::GUIEventAdapter::KEYUP):
|
|
{
|
|
if (ea.getKey()=='s')
|
|
{
|
|
saveSelectedModel(viewer->getSceneData());
|
|
}
|
|
else if (ea.getKey()=='o')
|
|
{
|
|
osg::notify(osg::NOTICE)<<"Saved model to file 'saved_model.osg'"<<std::endl;
|
|
osgDB::writeNodeFile(*(viewer->getSceneData()), "saved_model.osg");
|
|
}
|
|
else if (ea.getKey()=='p')
|
|
{
|
|
_usePolytopeIntersector = !_usePolytopeIntersector;
|
|
if (_usePolytopeIntersector)
|
|
{
|
|
osg::notify(osg::NOTICE)<<"Using PolytopeIntersector"<<std::endl;
|
|
} else {
|
|
osg::notify(osg::NOTICE)<<"Using LineSegmentIntersector"<<std::endl;
|
|
}
|
|
}
|
|
else if (ea.getKey()=='c')
|
|
{
|
|
_useWindowCoordinates = !_useWindowCoordinates;
|
|
if (_useWindowCoordinates)
|
|
{
|
|
osg::notify(osg::NOTICE)<<"Using window coordinates for picking"<<std::endl;
|
|
} else {
|
|
osg::notify(osg::NOTICE)<<"Using projection coordiates for picking"<<std::endl;
|
|
}
|
|
}
|
|
else if (ea.getKey()==osgGA::GUIEventAdapter::KEY_Delete || ea.getKey()==osgGA::GUIEventAdapter::KEY_BackSpace)
|
|
{
|
|
osg::notify(osg::NOTICE)<<"Delete"<<std::endl;
|
|
DeleteSelectedNodesVisitor dsnv;
|
|
viewer->getSceneData()->accept(dsnv);
|
|
dsnv.pruneSelectedNodes();
|
|
}
|
|
return false;
|
|
}
|
|
case(osgGA::GUIEventAdapter::PUSH):
|
|
case(osgGA::GUIEventAdapter::MOVE):
|
|
{
|
|
_mx = ea.getX();
|
|
_my = ea.getY();
|
|
return false;
|
|
}
|
|
case(osgGA::GUIEventAdapter::RELEASE):
|
|
{
|
|
if (_mx == ea.getX() && _my == ea.getY())
|
|
{
|
|
// only do a pick if the mouse hasn't moved
|
|
pick(ea,viewer);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void pick(const osgGA::GUIEventAdapter& ea, osgViewer::SimpleViewer* viewer)
|
|
{
|
|
osg::Node* scene = viewer->getSceneData();
|
|
if (!scene) return;
|
|
|
|
osg::notify(osg::NOTICE)<<std::endl;
|
|
|
|
osg::Node* node = 0;
|
|
osg::Group* parent = 0;
|
|
|
|
if (_usePolytopeIntersector)
|
|
{
|
|
osgUtil::PolytopeIntersector* picker;
|
|
if (_useWindowCoordinates)
|
|
{
|
|
// use window coordinates
|
|
// remap the mouse x,y into viewport coordinates.
|
|
osg::Viewport* viewport = viewer->getCamera()->getViewport();
|
|
double mx = viewport->x() + (int)((double )viewport->width()*(ea.getXnormalized()*0.5+0.5));
|
|
double my = viewport->y() + (int)((double )viewport->height()*(ea.getYnormalized()*0.5+0.5));
|
|
|
|
// half width, height.
|
|
double w = 5.0f;
|
|
double h = 5.0f;
|
|
picker = new osgUtil::PolytopeIntersector( osgUtil::Intersector::WINDOW, mx-w, my-h, mx+w, my+h );
|
|
} else {
|
|
double mx = ea.getXnormalized();
|
|
double my = ea.getYnormalized();
|
|
double w = 0.05;
|
|
double h = 0.05;
|
|
picker = new osgUtil::PolytopeIntersector( osgUtil::Intersector::PROJECTION, mx-w, my-h, mx+w, my+h );
|
|
}
|
|
osgUtil::IntersectionVisitor iv(picker);
|
|
|
|
viewer->getCamera()->accept(iv);
|
|
|
|
if (picker->containsIntersections())
|
|
{
|
|
osgUtil::PolytopeIntersector::Intersections& intersections = picker->getIntersections();
|
|
|
|
for (osgUtil::PolytopeIntersector::Intersections::iterator it=intersections.begin();
|
|
it!=intersections.end(); ++it) {
|
|
osgUtil::PolytopeIntersector::Intersection intersection=*it;
|
|
|
|
osg::NodePath& nodePath = intersection.nodePath;
|
|
node = (nodePath.size()>=1)?nodePath[nodePath.size()-1]:0;
|
|
parent = (nodePath.size()>=2)?dynamic_cast<osg::Group*>(nodePath[nodePath.size()-2]):0;
|
|
|
|
if (node) std::cout<<" Hits "<<node->className()<<" nodePath size"<<nodePath.size()<<std::endl;
|
|
toggleScribe(parent, node);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
osgUtil::LineSegmentIntersector* picker;
|
|
if (!_useWindowCoordinates)
|
|
{
|
|
// use non dimensional coordinates - in projection/clip space
|
|
picker = new osgUtil::LineSegmentIntersector( osgUtil::Intersector::PROJECTION, ea.getXnormalized(),ea.getYnormalized() );
|
|
} else {
|
|
// use window coordinates
|
|
// remap the mouse x,y into viewport coordinates.
|
|
osg::Viewport* viewport = viewer->getCamera()->getViewport();
|
|
float mx = viewport->x() + (int)((float)viewport->width()*(ea.getXnormalized()*0.5f+0.5f));
|
|
float my = viewport->y() + (int)((float)viewport->height()*(ea.getYnormalized()*0.5f+0.5f));
|
|
picker = new osgUtil::LineSegmentIntersector( osgUtil::Intersector::WINDOW, mx, my );
|
|
}
|
|
osgUtil::IntersectionVisitor iv(picker);
|
|
|
|
viewer->getCamera()->accept(iv);
|
|
|
|
if (picker->containsIntersections())
|
|
{
|
|
osgUtil::LineSegmentIntersector::Intersection intersection = picker->getFirstIntersection();
|
|
osg::notify(osg::NOTICE)<<"Picked "<<intersection.localIntersectionPoint<<std::endl;
|
|
|
|
osg::NodePath& nodePath = intersection.nodePath;
|
|
node = (nodePath.size()>=1)?nodePath[nodePath.size()-1]:0;
|
|
parent = (nodePath.size()>=2)?dynamic_cast<osg::Group*>(nodePath[nodePath.size()-2]):0;
|
|
|
|
if (node) std::cout<<" Hits "<<node->className()<<" nodePath size"<<nodePath.size()<<std::endl;
|
|
toggleScribe(parent, node);
|
|
}
|
|
}
|
|
|
|
// now we try to decorate the hit node by the osgFX::Scribe to show that its been "picked"
|
|
}
|
|
|
|
void toggleScribe(osg::Group* parent, osg::Node* node) {
|
|
if (!parent || !node) return;
|
|
|
|
std::cout<<" parent "<<parent->className()<<std::endl;
|
|
|
|
osgFX::Scribe* parentAsScribe = dynamic_cast<osgFX::Scribe*>(parent);
|
|
if (!parentAsScribe)
|
|
{
|
|
// node not already picked, so highlight it with an osgFX::Scribe
|
|
osgFX::Scribe* scribe = new osgFX::Scribe();
|
|
scribe->addChild(node);
|
|
parent->replaceChild(node,scribe);
|
|
}
|
|
else
|
|
{
|
|
// node already picked so we want to remove scribe to unpick it.
|
|
osg::Node::ParentList parentList = parentAsScribe->getParents();
|
|
for(osg::Node::ParentList::iterator itr=parentList.begin();
|
|
itr!=parentList.end();
|
|
++itr)
|
|
{
|
|
(*itr)->replaceChild(parentAsScribe,node);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void saveSelectedModel(osg::Node* scene)
|
|
{
|
|
if (!scene) return;
|
|
|
|
CreateModelToSaveVisitor cmtsv;
|
|
scene->accept(cmtsv);
|
|
|
|
if (cmtsv._group->getNumChildren()>0)
|
|
{
|
|
std::cout<<"Writing selected compoents to 'selected_model.osg'"<<std::endl;
|
|
osgDB::writeNodeFile(*cmtsv._group, "selected_model.osg");
|
|
}
|
|
}
|
|
|
|
protected:
|
|
|
|
float _mx,_my;
|
|
bool _usePolytopeIntersector;
|
|
bool _useWindowCoordinates;
|
|
};
|
|
|
|
int main( int argc, char **argv )
|
|
{
|
|
if (argc<2)
|
|
{
|
|
std::cout << argv[0] <<": requires filename argument." << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
// load the scene.
|
|
osg::ref_ptr<osg::Node> loadedModel = osgDB::readNodeFile(argv[1]);
|
|
if (!loadedModel)
|
|
{
|
|
std::cout << argv[0] <<": No data loaded." << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
// create the window to draw to.
|
|
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
|
|
traits->x = 200;
|
|
traits->y = 200;
|
|
traits->width = 800;
|
|
traits->height = 600;
|
|
traits->windowDecoration = true;
|
|
traits->doubleBuffer = true;
|
|
traits->sharedContext = 0;
|
|
|
|
osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());
|
|
osgViewer::GraphicsWindow* gw = dynamic_cast<osgViewer::GraphicsWindow*>(gc.get());
|
|
if (!gw)
|
|
{
|
|
osg::notify(osg::NOTICE)<<"Error: unable to create graphics window."<<std::endl;
|
|
return 1;
|
|
}
|
|
|
|
gw->realize();
|
|
gw->makeCurrent();
|
|
|
|
// create the view of the scene.
|
|
osgViewer::SimpleViewer viewer;
|
|
viewer.setSceneData(loadedModel.get());
|
|
|
|
viewer.setEventQueue(gw->getEventQueue());
|
|
viewer.getEventQueue()->windowResize(traits->x,traits->y,traits->width,traits->height);
|
|
|
|
// create a tracball manipulator to move the camera around in response to keyboard/mouse events
|
|
viewer.setCameraManipulator( new osgGA::TrackballManipulator );
|
|
|
|
osg::ref_ptr<osgGA::StateSetManipulator> statesetManipulator = new osgGA::StateSetManipulator;
|
|
statesetManipulator->setStateSet(viewer.getSceneView()->getGlobalStateSet());
|
|
viewer.addEventHandler(statesetManipulator.get());
|
|
|
|
// add the pick handler
|
|
viewer.addEventHandler(new PickHandler());
|
|
|
|
|
|
// add the exit handler'
|
|
ExitHandler* exitHandler = new ExitHandler;
|
|
viewer.addEventHandler(exitHandler);
|
|
|
|
viewer.init();
|
|
|
|
// main loop (note, window toolkits which take control over the main loop will require a window redraw callback containing the code below.)
|
|
while( gw->isRealized() && !exitHandler->done())
|
|
{
|
|
gw->checkEvents();
|
|
|
|
if (gw->isRealized() && !exitHandler->done())
|
|
{
|
|
viewer.frame();
|
|
|
|
// Swap Buffers
|
|
gw->swapBuffers();
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|