From e7ca7631fec82e481c94190d4474d757d04edeb5 Mon Sep 17 00:00:00 2001 From: logari81 Date: Tue, 4 Sep 2012 14:14:03 +0200 Subject: [PATCH] Sketcher: implement box selection (based on mrlukeparry's work) --- src/Gui/ViewProvider.cpp | 46 ++-- src/Gui/ViewProvider.h | 15 +- src/Mod/Sketcher/Gui/ViewProviderSketch.cpp | 281 ++++++++++++++++++-- src/Mod/Sketcher/Gui/ViewProviderSketch.h | 19 +- 4 files changed, 311 insertions(+), 50 deletions(-) diff --git a/src/Gui/ViewProvider.cpp b/src/Gui/ViewProvider.cpp index a65d3d2d9..b670ef4c6 100644 --- a/src/Gui/ViewProvider.cpp +++ b/src/Gui/ViewProvider.cpp @@ -142,29 +142,10 @@ void ViewProvider::setUpdatesEnabled (bool enable) void ViewProvider::eventCallback(void * ud, SoEventCallback * node) { const SoEvent * ev = node->getEvent(); - Gui::View3DInventorViewer* view = reinterpret_cast(node->getUserData()); + Gui::View3DInventorViewer* viewer = reinterpret_cast(node->getUserData()); ViewProvider *self = reinterpret_cast(ud); assert(self); - // Calculate 3d point to the mouse position - SbVec3f point, norm; - point = view->getPointOnScreen(ev->getPosition()); - norm = view->getViewDirection(); - - // for convenience make a pick ray action to get the (potentially) picked entity in the provider - //SoPickedPoint* Point = self->getPointOnRay(point,norm,*view); - SoSeparator* root = new SoSeparator; - root->ref(); - root->addChild(view->getCamera()); - root->addChild(self->pcRoot); - - SoRayPickAction rp(view->getViewportRegion()); - rp.setPoint(ev->getPosition()); - rp.apply(root); - root->unref(); - - SoPickedPoint* pp = rp.getPickedPoint(); - try { // Keyboard events if (ev->getTypeId().isDerivedFrom(SoKeyboardEvent::getClassTypeId())) { @@ -192,12 +173,12 @@ void ViewProvider::eventCallback(void * ud, SoEventCallback * node) const SbBool press = event->getState() == SoButtonEvent::DOWN ? TRUE : FALSE; // call the virtual method - if (self->mouseButtonPressed(button,press,point,norm,pp)) + if (self->mouseButtonPressed(button,press,ev->getPosition(),viewer)) node->setHandled(); } // Mouse Movement handling else if (ev->getTypeId().isDerivedFrom(SoLocation2Event::getClassTypeId())) { - if (self->mouseMove(point,norm,pp)) + if (self->mouseMove(ev->getPosition(),viewer)) node->setHandled(); } } @@ -352,11 +333,28 @@ PyObject* ViewProvider::getPyObject() return pyViewObject; } -SoPickedPoint* ViewProvider::getPointOnRay(const SbVec3f& pos,const SbVec3f& dir, const View3DInventorViewer& viewer) const +SoPickedPoint* ViewProvider::getPointOnRay(const SbVec2s& pos, const View3DInventorViewer* viewer) const +{ + // for convenience make a pick ray action to get the (potentially) picked entity in the provider + SoSeparator* root = new SoSeparator; + root->ref(); + root->addChild(viewer->getCamera()); + root->addChild(pcRoot); + + SoRayPickAction rp(viewer->getViewportRegion()); + rp.setPoint(pos); + rp.apply(root); + root->unref(); + + SoPickedPoint* pick = rp.getPickedPoint(); + return (pick ? new SoPickedPoint(*pick) : 0); +} + +SoPickedPoint* ViewProvider::getPointOnRay(const SbVec3f& pos,const SbVec3f& dir, const View3DInventorViewer* viewer) const { // Note: There seems to be a bug with setRay() which causes SoRayPickAction // to fail to get intersections between the ray and a line - SoRayPickAction rp(viewer.getViewportRegion()); + SoRayPickAction rp(viewer->getViewportRegion()); rp.setRay(pos,dir); rp.apply(pcRoot); diff --git a/src/Gui/ViewProvider.h b/src/Gui/ViewProvider.h index 7a22c7a89..0aeeee4e3 100644 --- a/src/Gui/ViewProvider.h +++ b/src/Gui/ViewProvider.h @@ -30,6 +30,7 @@ #include #include +class SbVec2s; class SbVec3f; class SoNode; class SoPath; @@ -220,11 +221,12 @@ public: /// is called by the tree if the user double click on the object virtual bool doubleClicked(void) { return false; } /// is called when the provider is in edit and the mouse is moved - virtual bool mouseMove(const SbVec3f &pos, const SbVec3f &norm, const SoPickedPoint* pp) + virtual bool mouseMove(const SbVec2s &cursorPos, + View3DInventorViewer* viewer) { return false; } /// is called when the Provider is in edit and the mouse is clicked - virtual bool mouseButtonPressed(int Button, bool pressed, const SbVec3f &pos, - const SbVec3f &norm, const SoPickedPoint* pp) + virtual bool mouseButtonPressed(int button, bool pressed, const SbVec2s &cursorPos, + const View3DInventorViewer* viewer) { return false; } /// set up the context-menu with the supported edit modes virtual void setupContextMenu(QMenu*, QObject*, const char*) {} @@ -264,8 +266,11 @@ protected: void setDefaultMode(int); //@} /// Helper method to get picked entities while editing - SoPickedPoint* getPointOnRay(const SbVec3f& pos,const SbVec3f& dir, - const View3DInventorViewer& viewer) const; + SoPickedPoint* getPointOnRay(const SbVec2s& pos, + const View3DInventorViewer* viewer) const; + /// Helper method to get picked entities while editing + SoPickedPoint* getPointOnRay(const SbVec3f& pos, const SbVec3f& dir, + const View3DInventorViewer* viewer) const; /// Reimplemented from subclass void onChanged(const App::Property* prop); diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index 16301c0d9..5511166a8 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -79,6 +79,7 @@ #include #include #include +#include #include #include #include @@ -115,6 +116,7 @@ SbColor ViewProviderSketch::SelectColor (0.11f,0.68f,0.11f); // #1CAD // Variables for holding previous click SbTime ViewProviderSketch::prvClickTime; SbVec3f ViewProviderSketch::prvClickPoint; +SbVec2s ViewProviderSketch::prvCursorPos; //************************************************************************** // Edit data structure @@ -182,8 +184,8 @@ struct EditData { SoCoordinate3 *RootCrossCoordinate; SoCoordinate3 *EditCurvesCoordinate; SoLineSet *CurveSet; - SoLineSet *EditCurveSet; SoLineSet *RootCrossSet; + SoLineSet *EditCurveSet; SoMarkerSet *PointSet; SoText2 *textX; @@ -348,11 +350,17 @@ void ViewProviderSketch::getCoordsOnSketchPlane(double &u, double &v,const SbVec v = S.y; } -bool ViewProviderSketch::mouseButtonPressed(int Button, bool pressed, const SbVec3f &point, - const SbVec3f &normal, const SoPickedPoint *pp) +bool ViewProviderSketch::mouseButtonPressed(int Button, bool pressed, const SbVec2s &cursorPos, + const Gui::View3DInventorViewer *viewer) { assert(edit); + // Calculate 3d point to the mouse position + SbVec3f point = viewer->getPointOnScreen(cursorPos); + SbVec3f normal = viewer->getViewDirection(); + + SoPickedPoint *pp = this->getPointOnRay(cursorPos, viewer); + // Radius maximum to allow double click event const int dblClickRadius = 5; @@ -375,11 +383,6 @@ bool ViewProviderSketch::mouseButtonPressed(int Button, bool pressed, const SbVe switch (Mode) { case STATUS_NONE:{ bool done=false; - // Double click events variables - SbTime tmp = (SbTime::getTimeOfDay() - prvClickTime); - float dci = (float) QApplication::doubleClickInterval()/1000.0f; - float length = (point - prvClickPoint).length(); - if (edit->PreselectPoint != -1) { //Base::Console().Log("start dragging, point:%d\n",this->DragPoint); Mode = STATUS_SELECT_Point; @@ -398,7 +401,13 @@ bool ViewProviderSketch::mouseButtonPressed(int Button, bool pressed, const SbVe done = true; } - if (done && length < dblClickRadius && tmp.getValue() < dci) { + // Double click events variables + float dci = (float) QApplication::doubleClickInterval()/1000.0f; + + if (done && + (point - prvClickPoint).length() < dblClickRadius && + (SbTime::getTimeOfDay() - prvClickTime).getValue() < dci) { + // Double Click Event Occured editDoubleClicked(); // Reset Double Click Static Variables @@ -409,6 +418,9 @@ bool ViewProviderSketch::mouseButtonPressed(int Button, bool pressed, const SbVe } else { prvClickTime = SbTime::getTimeOfDay(); prvClickPoint = point; + prvCursorPos = cursorPos; + if (!done) + Mode = STATUS_SKETCH_StartRubberBand; } return done; @@ -418,7 +430,7 @@ bool ViewProviderSketch::mouseButtonPressed(int Button, bool pressed, const SbVe default: return false; } - } else { + } else { // released // Do things depending on the mode of the user interaction switch (Mode) { case STATUS_SELECT_Point: @@ -582,6 +594,16 @@ bool ViewProviderSketch::mouseButtonPressed(int Button, bool pressed, const SbVe } Mode = STATUS_NONE; return true; + case STATUS_SKETCH_StartRubberBand: // a single click happened, so clear selection + Mode = STATUS_NONE; + Gui::Selection().clearSelection(); + return true; + case STATUS_SKETCH_UseRubberBand: + doBoxSelection(prvCursorPos, cursorPos, viewer); + // a redraw is required in order to clear the rubberband + draw(true); + Mode = STATUS_NONE; + return true; case STATUS_SKETCH_UseHandler: return edit->sketchHandler->releaseButton(Base::Vector2D(x,y)); case STATUS_NONE: @@ -740,12 +762,16 @@ void ViewProviderSketch::editDoubleClicked(void) } } -bool ViewProviderSketch::mouseMove(const SbVec3f &point, const SbVec3f &normal, const SoPickedPoint *pp) +bool ViewProviderSketch::mouseMove(const SbVec2s &cursorPos, Gui::View3DInventorViewer *viewer) { if (!edit) return false; assert(edit); + // Calculate 3d point to the mouse position + SbVec3f point = viewer->getPointOnScreen(cursorPos); + SbVec3f normal = viewer->getViewDirection(); + double x,y; getCoordsOnSketchPlane(x,y,point,normal); snapToGrid(x, y); @@ -753,6 +779,9 @@ bool ViewProviderSketch::mouseMove(const SbVec3f &point, const SbVec3f &normal, bool preselectChanged; if (Mode!=STATUS_SKETCH_DragPoint && Mode!=STATUS_SKETCH_DragCurve && Mode!=STATUS_SKETCH_DragConstraint) { + + SoPickedPoint *pp = this->getPointOnRay(cursorPos, viewer); + int PtIndex,GeoIndex,ConstrIndex,CrossIndex; preselectChanged = detectPreselection(pp,PtIndex,GeoIndex,ConstrIndex,CrossIndex); } @@ -858,6 +887,19 @@ bool ViewProviderSketch::mouseMove(const SbVec3f &point, const SbVec3f &normal, this->updateColor(); } return true; + case STATUS_SKETCH_StartRubberBand: { + Mode = STATUS_SKETCH_UseRubberBand; + return true; + } + case STATUS_SKETCH_UseRubberBand: { + // a redraw is required in order to clear any previous rubberband + draw(true); + viewer->drawRect(prvCursorPos.getValue()[0], + viewer->getGLWidget()->height() - prvCursorPos.getValue()[1], + cursorPos.getValue()[0], + viewer->getGLWidget()->height() - cursorPos.getValue()[1]); + return true; + } default: return false; } @@ -1362,6 +1404,214 @@ bool ViewProviderSketch::detectPreselection(const SoPickedPoint *Point, int &PtI return false; } +void ViewProviderSketch::doBoxSelection(const SbVec2s &startPos, const SbVec2s &endPos, + const Gui::View3DInventorViewer *viewer) +{ + std::vector corners0; + corners0.push_back(startPos); + corners0.push_back(endPos); + std::vector corners = viewer->getGLPolygon(corners0); + + // all calculations with polygon and proj are in dimensionless [0 1] screen coordinates + Base::Polygon2D polygon; + polygon.Add(Base::Vector2D(corners[0].getValue()[0], corners[0].getValue()[1])); + polygon.Add(Base::Vector2D(corners[0].getValue()[0], corners[1].getValue()[1])); + polygon.Add(Base::Vector2D(corners[1].getValue()[0], corners[1].getValue()[1])); + polygon.Add(Base::Vector2D(corners[1].getValue()[0], corners[0].getValue()[1])); + + Gui::ViewVolumeProjection proj(viewer->getCamera()->getViewVolume()); + + App::Document* doc = App::GetApplication().getActiveDocument(); + Sketcher::SketchObject *sketchObject = NULL; + if (doc) + sketchObject = dynamic_cast(doc->getActiveObject()); + if (!sketchObject) + return; + + Base::Placement Plm = sketchObject->Placement.getValue(); + + int intGeoCount = sketchObject->getHighestCurveIndex() + 1; + int extGeoCount = sketchObject->getExternalGeometryCount(); + + const std::vector geomlist = sketchObject->getCompleteGeometry(); // without memory allocation + assert(int(geomlist.size()) == extGeoCount + intGeoCount); + assert(int(geomlist.size()) >= 2); + + Base::Vector3d pnt0, pnt1, pnt2, pnt; + int VertexId = -1; // the loop below should be in sync with the main loop in ViewProviderSketch::draw + // so that the vertex indices are calculated correctly + int GeoId = 0; + for (std::vector::const_iterator it = geomlist.begin(); it != geomlist.end()-2; ++it, ++GeoId) { + + if (GeoId >= intGeoCount) + GeoId = -extGeoCount; + + if ((*it)->getTypeId() == Part::GeomPoint::getClassTypeId()) { + // ----- Check if single point lies inside box selection -----/ + const Part::GeomPoint *point = dynamic_cast(*it); + Plm.multVec(point->getPoint(), pnt0); + pnt0 = proj(pnt0); + VertexId += 1; + + if (polygon.Contains(Base::Vector2D(pnt0.x, pnt0.y))) { + std::stringstream ss; + ss << "Vertex" << VertexId; + Gui::Selection().addSelection(doc->getName(), sketchObject->getNameInDocument(), ss.str().c_str()); + } + + } else if ((*it)->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + // ----- Check if line segment lies inside box selection -----/ + const Part::GeomLineSegment *lineSeg = dynamic_cast(*it); + Plm.multVec(lineSeg->getStartPoint(), pnt1); + Plm.multVec(lineSeg->getEndPoint(), pnt2); + pnt1 = proj(pnt1); + pnt2 = proj(pnt2); + VertexId += 2; + + bool pnt1Inside = polygon.Contains(Base::Vector2D(pnt1.x, pnt1.y)); + bool pnt2Inside = polygon.Contains(Base::Vector2D(pnt2.x, pnt2.y)); + if (pnt1Inside) { + std::stringstream ss; + ss << "Vertex" << VertexId - 1; + Gui::Selection().addSelection(doc->getName(), sketchObject->getNameInDocument(), ss.str().c_str()); + } + + if (pnt2Inside) { + std::stringstream ss; + ss << "Vertex" << VertexId; + Gui::Selection().addSelection(doc->getName(), sketchObject->getNameInDocument(), ss.str().c_str()); + } + + if (pnt1Inside && pnt2Inside) { + std::stringstream ss; + ss << "Edge" << GeoId; + Gui::Selection().addSelection(doc->getName(), sketchObject->getNameInDocument(), ss.str().c_str()); + } + + } else if ((*it)->getTypeId() == Part::GeomCircle::getClassTypeId()) { + // ----- Check if circle lies inside box selection -----/ + const Part::GeomCircle *circle = dynamic_cast(*it); + pnt0 = circle->getCenter(); + VertexId += 1; + + Plm.multVec(pnt0, pnt0); + pnt0 = proj(pnt0); + + if (polygon.Contains(Base::Vector2D(pnt0.x, pnt0.y))) { + std::stringstream ss; + ss << "Vertex" << VertexId; + Gui::Selection().addSelection(doc->getName(), sketchObject->getNameInDocument(), ss.str().c_str()); + + int countSegments = 12; + float segment = float(2 * M_PI) / countSegments; + + // circumscribed polygon radius + float radius = float(circle->getRadius()) / cos(segment/2); + + bool bpolyInside = true; + pnt0 = circle->getCenter(); + float angle = 0.f; + for (int i = 0; i < countSegments; ++i, angle += segment) { + pnt = Base::Vector3d(pnt0.x + radius * cos(angle), + pnt0.y + radius * sin(angle), + 0.f); + Plm.multVec(pnt, pnt); + pnt = proj(pnt); + if (!polygon.Contains(Base::Vector2D(pnt.x, pnt.y))) { + bpolyInside = false; + break; + } + } + + if (bpolyInside) { + ss.clear(); + ss.str(""); + ss << "Edge" << GeoId; + Gui::Selection().addSelection(doc->getName(), sketchObject->getNameInDocument(),ss.str().c_str()); + } + } + + } else if ((*it)->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { + // Check if arc lies inside box selection + const Part::GeomArcOfCircle *aoc = dynamic_cast(*it); + + pnt0 = aoc->getCenter(); + pnt1 = aoc->getStartPoint(); + pnt2 = aoc->getEndPoint(); + VertexId += 3; + + Plm.multVec(pnt0, pnt0); + Plm.multVec(pnt1, pnt1); + Plm.multVec(pnt2, pnt2); + pnt0 = proj(pnt0); + pnt1 = proj(pnt1); + pnt2 = proj(pnt2); + + if (polygon.Contains(Base::Vector2D(pnt0.x, pnt0.y))) { + std::stringstream ss; + ss << "Vertex" << VertexId - 2; + Gui::Selection().addSelection(doc->getName(), sketchObject->getNameInDocument(), ss.str().c_str()); + } + + bool pnt1Inside = polygon.Contains(Base::Vector2D(pnt1.x, pnt1.y)); + if (pnt1Inside) { + std::stringstream ss; + ss << "Vertex" << VertexId - 1; + Gui::Selection().addSelection(doc->getName(), sketchObject->getNameInDocument(), ss.str().c_str()); + } + + bool pnt2Inside = polygon.Contains(Base::Vector2D(pnt2.x, pnt2.y)); + if (pnt2Inside) { + std::stringstream ss; + ss << "Vertex" << VertexId; + Gui::Selection().addSelection(doc->getName(), sketchObject->getNameInDocument(), ss.str().c_str()); + } + + if (pnt1Inside && pnt2Inside) { + double startangle, endangle; + aoc->getRange(startangle, endangle); + if (startangle > endangle) // if arc is reversed + std::swap(startangle, endangle); + + double range = endangle-startangle; + int countSegments = std::max(2, int(12.0 * range / (2 * M_PI))); + float segment = float(range) / countSegments; + + // circumscribed polygon radius + float radius = float(aoc->getRadius()) / cos(segment/2); + + bool bpolyInside = true; + pnt0 = aoc->getCenter(); + float angle = float(startangle) + segment/2; + for (int i = 0; i < countSegments; ++i, angle += segment) { + pnt = Base::Vector3d(pnt0.x + radius * cos(angle), + pnt0.y + radius * sin(angle), + 0.f); + Plm.multVec(pnt, pnt); + pnt = proj(pnt); + if (!polygon.Contains(Base::Vector2D(pnt.x, pnt.y))) { + bpolyInside = false; + break; + } + } + + if (bpolyInside) { + std::stringstream ss; + ss << "Edge" << GeoId; + Gui::Selection().addSelection(doc->getName(), sketchObject->getNameInDocument(), ss.str().c_str()); + } + } + + } else if ((*it)->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { + const Part::GeomBSplineCurve *spline = dynamic_cast(*it); + std::vector poles = spline->getPoles(); + VertexId += poles.size(); + // TODO + } + } + +} + void ViewProviderSketch::updateColor(void) { assert(edit); @@ -2543,7 +2793,7 @@ bool ViewProviderSketch::setEdit(int ModNum) // clear the selection (convenience) Gui::Selection().clearSelection(); - // create the container for the addtitional edit data + // create the container for the additional edit data assert(!edit); edit = new EditData(); @@ -2740,9 +2990,9 @@ void ViewProviderSketch::createEditInventorNodes(void) cursorTextColor.setPackedValue((uint32_t)hGrp->GetUnsigned("CursorTextColor", cursorTextColor.getPackedValue()), transparency); // stuff for the edit coordinates ++++++++++++++++++++++++++++++++++++++ - SoMaterial *EditMaterials = new SoMaterial; - EditMaterials->diffuseColor = cursorTextColor; - edit->EditRoot->addChild(EditMaterials); + SoMaterial *CoordTextMaterials = new SoMaterial; + CoordTextMaterials->diffuseColor = cursorTextColor; + edit->EditRoot->addChild(CoordTextMaterials); SoSeparator *Coordsep = new SoSeparator(); // no caching for fluctuand data structures @@ -2759,6 +3009,7 @@ void ViewProviderSketch::createEditInventorNodes(void) edit->textX->justification = SoText2::LEFT; edit->textX->string = ""; Coordsep->addChild(edit->textX); + edit->EditRoot->addChild(Coordsep); // group node for the Constraint visual +++++++++++++++++++++++++++++++++++ diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.h b/src/Mod/Sketcher/Gui/ViewProviderSketch.h index 4a850ae3c..d281ce597 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.h +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.h @@ -112,7 +112,9 @@ public: STATUS_SKETCH_DragPoint, /**< enum value while dragging a point. */ STATUS_SKETCH_DragCurve, /**< enum value while dragging a curve. */ STATUS_SKETCH_DragConstraint, /**< enum value while dragging a compatible constraint. */ - STATUS_SKETCH_UseHandler /**< enum value a DrawSketchHandler is in control. */ + STATUS_SKETCH_UseHandler, /**< enum value a DrawSketchHandler is in control. */ + STATUS_SKETCH_StartRubberBand, /**< enum value for initiating a rubber band selection */ + STATUS_SKETCH_UseRubberBand /**< enum value when making a rubber band selection *//**< enum value a DrawSketchHandler is in control. */ }; /// is called by GuiCommands to set the drawing mode void setSketchMode(SketchMode mode) {Mode = mode;} @@ -124,10 +126,14 @@ public: //@{ /// give the coordinates of a line on the sketch plane in sketcher (2D) coordinates void getCoordsOnSketchPlane(double &u, double &v,const SbVec3f &point, const SbVec3f &normal); - /// helper to detect preselection - //bool handlePreselection(const SoPickedPoint *pp); + /// helper to detect preselection bool detectPreselection(const SoPickedPoint *Point, int &PtIndex,int &GeoIndex, int &ConstrIndex, int &CrossIndex); + + /// box selection method + void doBoxSelection(const SbVec2s &startPos, const SbVec2s &endPos, + const Gui::View3DInventorViewer *viewer); + /// helper change the color of the sketch according to selection and solver status void updateColor(void); /// get the pointer to the sketch document object @@ -161,12 +167,12 @@ public: /// is called by the tree if the user double click on the object virtual bool doubleClicked(void); /// is called when the Provider is in edit and the mouse is moved - virtual bool mouseMove(const SbVec3f &pNear, const SbVec3f &pFar, const SoPickedPoint *pp); + virtual bool mouseMove(const SbVec2s &pos, Gui::View3DInventorViewer *viewer); /// is called when the Provider is in edit and a key event ocours. Only ESC ends edit. virtual bool keyPressed(bool pressed, int key); /// is called when the Provider is in edit and the mouse is clicked - virtual bool mouseButtonPressed(int Button, bool pressed, const SbVec3f &point, - const SbVec3f &normal, const SoPickedPoint *pp); + virtual bool mouseButtonPressed(int Button, bool pressed, const SbVec2s &pos, + const Gui::View3DInventorViewer *viewer); //@} friend class DrawSketchHandler; @@ -229,6 +235,7 @@ protected: static SbTime prvClickTime; static SbVec3f prvClickPoint; + static SbVec2s prvCursorPos; float zCross; float zLines;