diff options
Diffstat (limited to 'mayaPlug/shaveCursorCtx.cpp')
| -rw-r--r-- | mayaPlug/shaveCursorCtx.cpp | 1709 |
1 files changed, 1709 insertions, 0 deletions
diff --git a/mayaPlug/shaveCursorCtx.cpp b/mayaPlug/shaveCursorCtx.cpp new file mode 100644 index 0000000..7124cde --- /dev/null +++ b/mayaPlug/shaveCursorCtx.cpp @@ -0,0 +1,1709 @@ +// Shave and a Haircut +// (c) 2019 Epic Games +// US Patent 6720962 + +//Qt headers must be included before any others !!! +#include <QtCore/QEvent> +#include <QtGui/QMouseEvent> +#include <QtGui/QTabletEvent> +#include <QtGui/QWheelEvent> + +#if QT_VERSION < 0x050000 +# include <QtGui/QApplication> +# include <QtGui/QWidget> +#else +# include <QtWidgets/QApplication> +# include <QtWidgets/QWidget> +#endif + +#include <iostream> +#include <vector> +using namespace std; +#include <limits.h> + +#include <maya/M3dView.h> +#include <maya/MCursor.h> +#include <maya/MDagModifier.h> +#include <maya/MDagPath.h> +#include <maya/MEvent.h> +#include <maya/MEventMessage.h> +#include <maya/MFnCamera.h> +#include <maya/MFnDagNode.h> +#include <maya/MFnTransform.h> +#include <maya/MGlobal.h> +#include <maya/MPoint.h> +#include <maya/MPxContext.h> +#include <maya/MSelectionList.h> +#include <maya/MString.h> +#include <maya/MVector.h> + +#include "shaveSDK.h" +#include "shaveCursorCtx.h" +#include "shaveBrushManip.h" +#include "shaveGlobals.h" +#include "shaveHairShape.h" +#include "shaveHairUI.h" +#include "shaveUtil.h" +#include "shaveStyleCmd.h" + +#ifdef OSMac_ +#include "shaveMacCarbon.h" +#endif + +#include <vector> + +// +// Qt Mouse Watcher -- vlad|17Mar2010 +// +static bool toolActive = false; +//static bool toolJustMoved = false; //gah, need to set it per node +static bool gMouseDown = false; +static bool eventsHappen = false; +std::vector<shaveHairShape*> shnodes; + +bool IsToolActive() +{ + return toolActive; +} +//bool IsToolJustMoved() +//{ +// return toolJustMoved; +//} +bool GetEventsHappen() +{ + return eventsHappen; +} + +void SetEventsHappen() +{ + eventsHappen = true; + return; +} + + +void ClearEvents() +{ + eventsHappen = false; +} +bool IsMouseDown() +{ + return gMouseDown; +} + +bool shaveQtMouseWatcher::m_busy = false; +bool shaveQtMouseWatcher::eventFilter(QObject* obj, QEvent* event) +{ + if (event->type() == (QEvent::Type)NotBusyEvent::aType) + { + m_busy = false; + event->accept(); + } + if (event->type() == QEvent::MouseMove || + event->type() == QEvent::TabletMove) + { + //lets try to dirty flag on any mouse move event + + SetEventsHappen(); + event->accept(); + + QPoint sp; + QPoint lp; + //couple words about event: + //LINUX: free move and move events are fired by view + //OSX: drag events are fired by view but free move eviets are fired by + //window that view, so local position (5,6) turns into (5,28) + //in other words in-view test I did (viewLocation == localLocation) below + //is not ok for OSX. + bool tablet = false; + if (event->type() == QEvent::MouseMove) + { + m_busy = false; //just an insurrance + + //if(m_busy) + //{ + // return false; + //} + //if(!m_busy) + //{ + // m_busy = true; + // QEvent* bev = new NotBusyEvent(); + // QApplication::postEvent(qApp->activeWindow(), bev); + //} + + SetEventsHappen(); + + QMouseEvent* me = (QMouseEvent*)event; + sp = me->globalPos(); + lp = me->pos(); + + //printf("--- pos %i %i\n",sp.x(),sp.y());fflush(stdout); + } + else if (event->type() == QEvent::TabletMove) + { + SetEventsHappen(); + if(m_busy) + { + return false; + } + if(!m_busy) + { + m_busy = true; + QEvent* bev = new NotBusyEvent(); + QApplication::postEvent(qApp->activeWindow(), bev); + } + + tablet = true; + + QTabletEvent* te = (QTabletEvent*)event; + sp = te->globalPos(); + lp = te->pos(); + + //there is pretty consistent + //printf("get pos %i %i\n",lp.x(),lp.y());fflush(stdout); + //printf("get pos %i %i\n",sp.x(),sp.y());fflush(stdout); + } + //char buf[200]; + //sprintf(buf,"lp: %i %i",lp.x(),lp.y()); + //MGlobal::displayInfo(buf); + + bool inview = false; + //shave tracks only in active view, so no need to iterate all + //unsigned int nv = M3dView::numberOf3dViews(); + //for(unsigned int i = 0; i < nv; i++) + { + //M3dView theview; + //MStatus stat = M3dView::get3dView(i,theview); + + MStatus stat; + M3dView theview = M3dView::active3dView(&stat); + if(stat == MStatus::kSuccess) + { + //unsigned int vw = theview.portWidth(); + //unsigned int vh = theview.portHeight(); + //if(vw == 0 || vh == 0) + // continue; + + QWidget* qw = theview.widget(&stat); + if(stat == MStatus::kSuccess && qw != NULL) + { + QPoint wp = qw->mapFromGlobal(sp); + + //char buf[200]; + //sprintf(buf,"wp%i: %i %i",i,wp.x(),wp.y()); + //MGlobal::displayInfo(buf); + + // not good for OSX, see note above + // additional tests needed + //if(lp == wp) + //{ + // inview = true; + // //MGlobal::displayInfo(MString("view:") + i); + // m_ctx->pointerMoved(theview.window(),lp.x(),lp.y()); + // break; + //} + if(wp.x() >= 0 && wp.y() >= 0 && + wp.x() < theview.portWidth() && + wp.y() < theview.portHeight()) + { + inview = true; + m_ctx->pointerMoved(theview.window(),wp.x(),wp.y()); + //printf("set pos %i %i\n",wp.x(),wp.y());fflush(stdout); + //break; + } + } + } + } + if(!inview) + { + m_ctx->leaveWindow(NULL,0,0); //x,y are not used in leaveWindow + } + } + + if(incrementaldraw ) + return false; + + return QObject::eventFilter(obj, event); +} + +static shaveQtMouseWatcher* mv = NULL; + +static globalQtViewport20Tracker* vp20t = NULL; +void CheckGlobalQtViewport20Tracker() +{ + if(vp20t == NULL) + { + vp20t = new globalQtViewport20Tracker(); + qApp->installEventFilter(vp20t); + } + +} +bool globalQtViewport20Tracker::eventFilter(QObject* obj, QEvent* event) +{ + if (event->type() == QEvent::MouseMove || + event->type() == QEvent::TabletMove ) + { + //MGlobal::displayInfo("mouse moved"); + M3dView view = M3dView::active3dView(); + + MDagPath camPath; + MStatus stat = MStatus::kSuccess; + stat = view.getCamera(camPath); + if(stat == MStatus::kSuccess) + { + MFnCamera camFn(camPath); + MMatrix tm = camPath.inclusiveMatrixInverse(); + if(worldToCam != tm) + { + + MDagPathArray paths; + shaveUtil::getShaveNodes(paths); //it should not trigger ::compute + for (unsigned int i = 0; i < paths.length(); i++) + { + //MGlobal::displayInfo("vp2.0 dirtied"); + MHWRender::MRenderer::setGeometryDrawDirty(paths[i].node()); + } + worldToCam = tm; + } + } +#ifdef OSMac_ + //need to diryt vp2 on OSX ( for the case of growth obj moved) + MDagPathArray paths; + shaveUtil::getShaveNodes(paths); //it should not trigger ::compute + for (unsigned int i = 0; i < paths.length(); i++) + { + //MGlobal::displayInfo("vp2.0 dirtied"); + MHWRender::MRenderer::setGeometryDrawDirty(paths[i].node()); + } +#endif + + } + if (event->type() == QEvent::MouseButtonRelease) + { + MDagPathArray paths; + shaveUtil::getShaveNodes(paths); //it should not trigger ::compute + for (unsigned int i = 0; i < paths.length(); i++) + MHWRender::MRenderer::setGeometryDrawDirty(paths[i].node()); + } + + return QObject::eventFilter(obj, event); +} + +#ifdef GLOBAL_FALLBACK + +//static LARGE_INTEGER last={ static_cast<LONGLONG>(0) }; +//LARGE_INTEGER GetLastMoveTime() {return last;} + +static qint64 last; +qint64 GetLastMoveTime() {return last;} +void SetLastMoveTime(qint64 t) {last = t;} + +bool globalQtMouseWatcher::eventFilter(QObject* obj, QEvent* event) +{ + //printf("event type %i\n",event->type());fflush(stdout); +if (event->type() == QEvent::Wheel )eventsHappen = true; + + if (event->type() == QEvent::MouseMove || + event->type() == QEvent::TabletMove ) +// event->type() == QEvent::Wheel ) + { +// printf("mouse move (pressed: %s)\n",gMouseDown?"yes":"no"); + + if(gMouseDown) + { + if(!eventsHappen) + { +// printf("flush 01\n");fflush(stdout); +// qApp->flush(); + } + eventsHappen = true; + } + + + if(!incrementaldraw ) + event->accept(); + + shaveGlobals::getGlobals(); + + if((toolActive && !gMouseDown && doFallbackGlob)||(event->type() == QEvent::Wheel && doFallbackGlob )) + { + eventsHappen = true; + //gah, hungs. probably need to make the list of nodes in tool init + + // MFnDagNode nodeFn; + // MDagPathArray paths; + // shaveUtil::getShaveNodes(paths); //it should not trigger ::compute + // for (unsigned int i = 0; i < paths.length(); i++) + // { + // nodeFn.setObject(paths[i].node()); + + // if (nodeFn.typeId() == shaveHairShape::id) + // { + // shaveHairShape* shape = (shaveHairShape*)nodeFn.userNode(); + // shape->dirties.BRUSH_JUST_MOVED = 1; + // } + // } + //QueryPerformanceCounter(&last); + if(!incrementaldraw ) + last = GetQTimer().elapsed(); + + if(!incrementaldraw ) + for(unsigned int i = 0; i < shnodes.size(); i++) + { + if(shnodes[i]->dirties.BRUSH_JUST_MOVED == 0) + { + shnodes[i]->dirtyDisplay(); //its idiocy + shnodes[i]->dirties.BRUSH_JUST_MOVED = 1; + } + } + //if(!incrementaldraw ) + //if(shaveStyleCmd::redrawOnIdlDone) + //{ + // printf("redrawOnIdle fired\n");fflush(stdout); + + // shaveStyleCmd::redrawOnIdlDone = false; + // MGlobal::executeCommandOnIdle("shaveStyle -redrawOnIdle"); + //} +// MHWRender::MRenderer::setGeometryDrawDirty(paths[i].node()); + } + qint64 t = GetQTimer().elapsed(); + SetLastMoveTime(t); + //////////////////////// + if(incrementaldraw ) + { + + if(shaveStyleCmd::redrawOnIdlDone) + { + //printf("redrawOnIdle fired\n");fflush(stdout); + + shaveStyleCmd::redrawOnIdlDone = false; + MGlobal::executeCommandOnIdle("shaveStyle -redrawOnIdle"); + + //nope. does not make better on tex assigment + //MGlobal::executeCommandOnIdle("shaveStyle -fullRedrawOnIdle"); + } + } + } + if (event->type() == QEvent::MouseButtonPress || + event->type() == QEvent::TabletPress|| + event->type() == /*QEvent::KeyPress*/ 6) + { + if(!incrementaldraw ) + event->accept(); + + +// printf("flush 00\n");fflush(stdout); +// qApp->flush(); + + gMouseDown = true; + eventsHappen = true; + + MFnDagNode nodeFn; + MDagPathArray paths; + shaveUtil::getShaveNodes(paths); + if(!incrementaldraw ) + for (unsigned int i = 0; i < paths.length(); i++) + { + nodeFn.setObject(paths[i].node()); + + if (nodeFn.typeId() == shaveHairShape::id) + { + //////////////////////// + //printf("MOUSE DOWN\n"); fflush(stdout); + //////////////////////// + shaveHairShape* shape = (shaveHairShape*)nodeFn.userNode(); + shape->dirties.GLOBAL_MOUSE_DOWN = 1; + + MHWRender::MRenderer::setGeometryDrawDirty(paths[i].node()); + } + } + + //////////////////////////////////////// + //MSelectionList selection; + //MGlobal::getActiveSelectionList(selection); + + //MObject comp; + //bool componentMode = (MGlobal::selectionMode()== MGlobal::kSelectComponentMode); + //unsigned i; + //MDagPath path; + //MFnDagNode nodeFn; + + //for (i = 0; i < selection.length(); i++) + //{ + // if (selection.getDagPath(i, path, comp) && path.isValid()) + // { + // path.extendToShape(); + + // // + // // If we're in component selection mode then ignore any + // // nodes which don't have components selected. + // // + // if (!componentMode || !comp.isNull()) + // { + // nodeFn.setObject(path.node()); + + // if (nodeFn.typeId() == shaveHairShape::id) + // { + // shaveHairShape* shape = (shaveHairShape*)nodeFn.userNode(); + // shape->dirties.GLOBAL_MOUSE_DOWN = 1; + // break; + // } + // } + // } + //} + //////////////////////////////////////// + } + if (event->type() == QEvent::MouseButtonRelease || + event->type() == QEvent::TabletRelease|| + event->type() == /*QEvent::KeyRelease*/ 7) + { + if(!incrementaldraw ) + event->accept(); + + gMouseDown = false; + //eventsHappen = true; + + //shaveGlobals::Globals globals; + //shaveGlobals::getGlobals(globals); + + //////////////////////////////////////// + //MFnDagNode nodeFn; + //MDagPathArray paths; + //shaveUtil::getShaveNodes(paths); + //for (unsigned int i = 0; i < paths.length(); i++) + //{ + // nodeFn.setObject(paths[i].node()); + + // if (nodeFn.typeId() == shaveHairShape::id) + // { + + // //////////////////////// + // //printf("MOUSE UP\n"); fflush(stdout); + // //////////////////////// + + // shaveHairShape* shape = (shaveHairShape*)nodeFn.userNode(); + // shape->dirties.GLOBAL_MOUSE_DOWN = 0; + // shape->dirties.DIRTY_TEXTURE = 1; + // shape->dirties.DIRTY_TEXTURE_JOE = 1; + // shape->dirtyDisplay(); + + // //how to avoid it? + // float trigger; + // MPlug triggerPlug = nodeFn.findPlug("trigger"); + // triggerPlug.getValue(trigger); + // triggerPlug.setValue(trigger+1.0f); + // } + //} + ///////////////////aaaaaahh/////////////////// + //MFnDagNode nodeFn; + //MDagPathArray paths; + //shaveUtil::getShaveNodes(paths); + //for (unsigned int i = 0; i < paths.length(); i++) + //{ + // nodeFn.setObject(paths[i].node()); + // if (nodeFn.typeId() == shaveHairShape::id) + // { + // shaveHairShape* shape = (shaveHairShape*)nodeFn.userNode(); + // shape->updateTexLookups(); + // } + //} + //////////////////////////////////////// + //if(shaveStyleCmd::redrawOnIdlDone) + eventsHappen = true; + //if(!incrementaldraw ) + if(doFallbackGlob) + { + //printf("fullRedrawOnIdle fired\n");fflush(stdout); + + shaveStyleCmd::redrawOnIdlDone = false; + MGlobal::executeCommandOnIdle("shaveStyle -fullRedrawOnIdle"); + } + + //////////////////////////////////// + } + if(incrementaldraw ) + return false; + + return QObject::eventFilter(obj, event); +} + +static globalQtMouseWatcher* gmv = NULL; + +void CheckAndSetGlobalQtWatcher() +{ + if(gmv == NULL) + { + gmv = new globalQtMouseWatcher(); + qApp->installEventFilter(gmv); + } + StartQTimer(); +} + +static QElapsedTimer qTimer; +static bool qTimerStarted = false; +void StartQTimer() +{ + if(!qTimerStarted) + { + qTimer.start(); + qTimerStarted = true; + } +} +QElapsedTimer& GetQTimer() +{ + return qTimer; +} +#endif + + +#ifdef _WIN32 +MCursor shaveCursorCtx::mDoubleArrowCursor("shaveDoubleArrowCursor.cur"); +#else +# ifdef OSMac_ +# include "icons/shaveDoubleArrowCursorOSX.xbm" +# else +# include "icons/shaveDoubleArrowCursor.xbm" +# endif + +MCursor shaveCursorCtx::mDoubleArrowCursor( + shaveDoubleArrowCursor_width, + shaveDoubleArrowCursor_height, + 8, + 4, + (unsigned char*)shaveDoubleArrowCursor_bits, + (unsigned char*)shaveDoubleArrowCursor_bits + ); +#endif + +shaveCursorCtx* shaveCursorCtx::mActiveCtx = 0; + + +shaveCursorCtx::shaveCursorCtx() +: mActiveViewChangedId(0) +, mBrushSize(0.2f) +, mBrushStren(1.0f) +, mDoingStroke(false) +, mEventWindow(0) +, mFalloffEnabled(true) +, mFastBrush(true) +, mIsActive(false) +, mIsResizing(false) +, mManip(0) +, mResizeKeyPressed(false) +#ifdef _WIN32 +, mTrackingLeaveWindow(false) +#endif +{ +} + + +shaveCursorCtx::~shaveCursorCtx() +{ +} + + +MStatus shaveCursorCtx::doDrag(MEvent& event) +{ +#ifdef DO_PROFILE + if(!Profile::GetDiagFile()) + Profile::ProfileStart(NULL); + Profile::ProfileDump("shaveCursorCtx::doDrag", NULL); +#endif + + MStatus st = MS::kSuccess; + + if (!mManip) return st; + + short x; + short y; + + event.getPosition(x, y); + + if ((x != mStrokeLastX) || (y != mStrokeLastY)) + { + mStrokeLastX = x; + mStrokeLastY = y; +#ifdef DO_PROFILE + Profile::ProfileDump("doDrag 01", NULL); +#endif + M3dView view = M3dView::active3dView(); + + if (mIsResizing || !mDoingStroke) + { + if (!mFastBrush) + { + // Maya doesn't do refreshes during a drag so we must + // force a refresh so that the brush is redrawn at its new + // position. (That's not a problem for the fast brush + // because it draws itself independent of Maya's refresh.) + view.refresh(false, true); + } + + return st; + } +#ifdef DO_PROFILE + Profile::ProfileDump("doDrag 02", NULL); +#endif + MPoint worldMouse = getWorldPt(view, x, y); + + float maxX = (float)(view.portWidth() - 1); + float maxY = (float)(view.portHeight() - 1); + VERT screenDelta; + VERT screenPos; + VERT worldDelta; + VERT worldPos; + + screenDelta.x = (float)(x - mStrokeStartX) / maxX; + screenDelta.y = (float)(y - mStrokeStartY) / maxY; + screenDelta.z = 0.0f; + + screenPos.x = (float)x / maxX; + screenPos.y = (float)y / maxY; + screenPos.z = 0.0f; + + worldDelta.x = (float)worldMouse.x - mStrokeStartWorld.x; + worldDelta.y = (float)worldMouse.y - mStrokeStartWorld.y; + worldDelta.z = (float)worldMouse.z - mStrokeStartWorld.z; + + worldPos.x = (float)worldMouse.x; + worldPos.y = (float)worldMouse.y; + worldPos.z = (float)worldMouse.z; + + ////////////////////// + screenDelta.x *= getBrushStren(); + screenDelta.y *= getBrushStren(); + + screenPos.x = mStrokeStartX / maxX + screenDelta.x; + screenPos.y = mStrokeStartY / maxY + screenDelta.y; + + worldDelta.x *= getBrushStren(); + worldDelta.y *= getBrushStren(); + worldDelta.z *= getBrushStren(); + + worldPos.x = mStrokeStartWorld.x + worldDelta.x; + worldPos.y = mStrokeStartWorld.y + worldDelta.y; + worldPos.z = mStrokeStartWorld.z + worldDelta.z; + ////////////////////// + st = strokeDrag(screenPos, worldPos, screenDelta, worldDelta); + if (st) + { + // + // Let the hair node know that there have been changes. + // + mTargetShape->guidesChanged(); + // + // The call above will have let Maya know that a redraw is + // required, but Maya won't do one while we're dragging, so we have + // to force it. + // + view.refresh(false, true); + //what it try do not force + //view.refresh(false, false); //nothing changes + } + else + cleanupStroke(); + + mTargetShape->dirtyDisplay(); + +#if 0 + ////////////////////// + //mTargetShape->applyEdits(false); + ////////////////////// + if(mTargetShape) + { + //SHAVEsculpt_finish(); + mTargetShape->updateParams(); + mTargetShape->applyEdits(true); + //mTargetShape->getHairNode(); + + MSelectionList selection; + MGlobal::getActiveSelectionList(selection); + + MObject comp; + bool componentMode = (MGlobal::selectionMode() + == MGlobal::kSelectComponentMode); + unsigned i; + MDagPath path; + MFnDagNode nodeFn; + + for (i = 0; i < selection.length(); i++) + { + if (selection.getDagPath(i, path, comp) && path.isValid()) + { + path.extendToShape(); + + // + // If we're in component selection mode then ignore any + // nodes which don't have components selected. + // + if (!componentMode || !comp.isNull()) + { + nodeFn.setObject(path.node()); + + + float trigger; + MPlug triggerPlug = nodeFn.findPlug("trigger"); + triggerPlug.getValue(trigger); + triggerPlug.setValue(trigger+1.0f); + } + } + } + + M3dView::active3dView().refresh(); + } + ////////////////////// +#endif + } +#ifdef DO_PROFILE + Profile::ProfileDump("shaveCursorCtx::doDrag - done", NULL); +#endif + return st; +} + + +MStatus shaveCursorCtx::doPress(MEvent& event) +{ + MStatus st = MS::kSuccess; + + mDoingStroke = false; + + if (!mManip) return MS::kSuccess; + + // Save the start of the stroke. + // + event.getPosition(mStrokeStartX, mStrokeStartY); + + mStrokeLastX = mStrokeStartX; + mStrokeLastY = mStrokeStartY; + + // + // If the resizing hotkey is pressed, then go into resize mode. + // + if (mResizeKeyPressed) + { + mStrokeStartBrushSize = mBrushSize; + mIsResizing = true; + mDoingStroke = true; + } + else + { + // + // The first hair node on the selection list is the one we'll be + // modifying. There shouldn't be any others but if there are we'll + // ignore them because Shave can only handle editing on one hair + // node at a time. + // + MSelectionList selection; + MGlobal::getActiveSelectionList(selection); + + MObject comp; + bool componentMode = (MGlobal::selectionMode() + == MGlobal::kSelectComponentMode); + unsigned i; + MDagPath path; + MFnDagNode nodeFn; + + for (i = 0; i < selection.length(); i++) + { + if (selection.getDagPath(i, path, comp) && path.isValid()) + { + path.extendToShape(); + + // + // If we're in component selection mode then ignore any + // nodes which don't have components selected. + // + if (!componentMode || !comp.isNull()) + { + nodeFn.setObject(path.node()); + + if (nodeFn.typeId() == shaveHairShape::id) + { + mTargetShape = (shaveHairShape*)nodeFn.userNode(); + break; + } + } + } + } + + if (i == selection.length()) return MS::kSuccess; + + int rx; + int ry; + + mManip->getBrushRadii(rx, ry); + + float aspect = (float)rx / (float)ry; + float rSquared = (float)(rx * rx); + + M3dView view = M3dView::active3dView(); + + // + // Processing the selection list is slow. Let's get the + // already-processed selections from the hair node instead. + // + mTargetShape->makeCurrent(); //maybe better to place it in tool init? + + const shaveHairShape::Guides& cachedGuides = mTargetShape->getGuides().guides; + + SOFTGUIDE guide; + int g; + int v; + MPoint centroid; + unsigned numVerts = 0; + + for (g = 0; SHAVEfetch_guide(g, &guide) != -1; g++) + { + if (!guide.hidden) + { + float maxWeight = -1.0f; + + // + // If we're not in component mode, then we treat every vert as + // being selected. + // + for (v = 1; v < SHAVE_VERTS_PER_GUIDE; v++) + { + if (!componentMode || (cachedGuides[g].select & (1 << v))) + { + // + // Get the position of the vertex in window coords. + // + MPoint p(guide.guide[v].x, guide.guide[v].y, guide.guide[v].z); + short px; + short py; + + view.worldToView(p, px, py); + + // + // How far is this vertex from the center of the brush? + // + float dx = (float)(px - mStrokeStartX); + float dy = aspect * (float)(py - mStrokeStartY); + float dSquared = dx * dx + dy * dy; + + if (dSquared >= rSquared) + guide.select[v] = 0; + else + { + guide.select[v] = 1; + + if (mFalloffEnabled) + { + guide.weight[v] = 1.0f - dSquared / rSquared; + + if (guide.weight[v] > maxWeight) + maxWeight = guide.weight[v]; + } + else + { + guide.weight[v] = 1.0f; + maxWeight = 1.0f; + } + + centroid.x += p.x; + centroid.y += p.y; + centroid.z += p.z; + numVerts++; + } + } + else + guide.select[v] = 0; + } + + // + // If any verts on the guide were selected then select the root + // vert as well and give it a weight identical to the max + // assigned to any of the other verts. + // + if (maxWeight >= 0.0f) + { + guide.select[0] = 1; + guide.weight[0] = maxWeight; + } + else + guide.select[0] = 0; + + SHAVEput_guideNOCALC(g, &guide); + } + } + + if (numVerts > 0) + { + centroid = centroid / (double)numVerts; + + // + // How far along the eye vector, between the near and far clipping + // planes, does the centroid lie? + // + short centroidX; + short centroidY; + MPoint farClip; + MPoint nearClip; + + view.worldToView(centroid, centroidX, centroidY); + view.viewToWorld(centroidX, centroidY, nearClip, farClip); + + double totalDist = (farClip - nearClip).length(); + + // + // We should never get a totalDist of zero, but let's be safe. + // + if (totalDist > 0.0) + { + double centroidDist = (centroid - nearClip).length(); + + mCentroidFraction = centroidDist / totalDist; + + MPoint worldMouse = getWorldPt( + view, mStrokeStartX, mStrokeStartY + ); + + float maxX = (float)(view.portWidth() - 1); + float maxY = (float)(view.portHeight() - 1); + VERT screenPos; + VERT worldPos; + + screenPos.x = (float)mStrokeStartX / maxX; + screenPos.y = (float)mStrokeStartY / maxY; + screenPos.z = 0.0f; + + worldPos.x = (float)worldMouse.x; + worldPos.y = (float)worldMouse.y; + worldPos.z = (float)worldMouse.z; + + mStrokeStartWorld = worldPos; + + // + // Get the camera's position and viewing vector. + // + MDagPath cameraPath; + view.getCamera(cameraPath); + + MFnCamera cameraFn(cameraPath); + MPoint eyePoint = cameraFn.eyePoint(MSpace::kWorld); + MVector upDir = cameraFn.upDirection(MSpace::kWorld); + MVector viewDir = cameraFn.viewDirection(MSpace::kWorld); + + VERT eyePointAsVERT; + eyePointAsVERT.x = (float)eyePoint.x; + eyePointAsVERT.y = (float)eyePoint.y; + eyePointAsVERT.z = (float)eyePoint.z; + + VERT viewDirAsVERT; + viewDirAsVERT.x = (float)viewDir.x; + viewDirAsVERT.y = (float)viewDir.y; + viewDirAsVERT.z = (float)viewDir.z; + + VERT upDirAsVERT; + upDirAsVERT.x = (float)upDir.x; + upDirAsVERT.y = (float)upDir.y; + upDirAsVERT.z = (float)upDir.z; + + st = strokeBegin( + eyePointAsVERT, + viewDirAsVERT, + upDirAsVERT, + screenPos, + worldPos + ); + + if (st) + { + mDoingStroke = true; + + // + // To speed up interaction, we hide a lot of + // unnecessary stuff during the stroke, so that redraws + // are fast. + // + //hideHair(); not higging, but decreasing display haircount + + if(mTargetShape) + { + //mTargetShape->setBrushDown(true); //currently used for higing hair in vp2.0 + mTargetShape->dirties.BRUSH_MOUSE_DOWN = 1; + M3dView::active3dView().refresh(false, true); + } + } + else + { + // + // If beginStroke() returns kEndOfFile it means that + // the method succeeded, but the rest of the stroke + // is to be ignored. Typically used for instantaneous + // actions which occur on mouse-down. + // + if (st == MS::kEndOfFile) + { + cleanupStroke(); + st = MS::kSuccess; + } + } + } + } + } + + + return st; +} + + +MStatus shaveCursorCtx::doRelease(MEvent& event) +{ + MStatus st; + + if (!mManip || !mDoingStroke) return MS::kSuccess; + + // + // It may be possible for the release to occur at a different position + // from the last drag, so let's do a drag update first. + // + st = doDrag(event); + + if (mIsResizing) + { + mIsResizing = false; + + // + // If the resizing hotkey is no longer pressed, reset the cursor. + // + if (!mResizeKeyPressed) setCursor(MCursor::defaultCursor); + + // + // Update the property sheet to reflect the new brush size. + // + MGlobal::executeCommand("shaveCursorCtx_updateCommonPropertySheet"); + } + else + { + if (st) strokeEnd(); + eventsHappen = false; + cleanupStroke(); + } + + mDoingStroke = false; + // come back + + if(mTargetShape) + { + //mTargetShape->setBrushDown(false); //in vp2.0 + mTargetShape->dirties.BRUSH_MOUSE_DOWN = 0; + M3dView::active3dView().refresh(); + } + return st; +} + + +void shaveCursorCtx::resizeKeyPressed(bool isPressed) +{ + if (mResizeKeyPressed != isPressed) + { + mResizeKeyPressed = isPressed; + + // + // If we're not currently doing a stroke, then set the cursor to + // the double-headed arrow when the key pressed, and back to normal + // when it is released. + // + if (!mDoingStroke) + { + if (isPressed) + setCursor(mDoubleArrowCursor); + else + setCursor(MCursor::defaultCursor); + } + } +} + + +void shaveCursorCtx::setBrushSize(float size) +{ + if (size != mBrushSize) + { + mBrushSize = size; + + if (mManip) + { + mManip->setBrushSize(mBrushSize); + + if (mIsActive) mManip->redrawBrush(); + } + } +} + + +void shaveCursorCtx::toolOffCleanup() +{ + // + // Let the hair nodes know that the brush is no longer active. + // + toolActive = false; + + shaveHairUI::setBrushActive(false); + //for vp2.0 + if (mTargetShape != NULL) mTargetShape->setBrushActive(false); + + + mIsActive = false; + + MGlobal::executeCommand("shaveBrush_setHotkeys off"); + uncatchActiveViewEvents(); + MMessage::removeCallback(mActiveViewChangedId); + + if (mManip) + { + mManip->setActive(false); + mManip = NULL; + } + + deleteManipulators(); + mActiveCtx = 0; + + if (!mFastBrush) + { + MSelectionList list; + MObject dummy; + list.add("shaveBrushProxy"); + + if (list.length() > 0) + { + list.getDependNode(0, dummy); + + MDagModifier dagMod; + dagMod.deleteNode(dummy); + dagMod.doIt(); + } + } +} + + +void shaveCursorCtx::toolOnSetup(MEvent& event) +{ + //////////////////////////////////// + toolActive = true; +#ifdef GLOBAL_FALLBACK + shaveStyleCmd::redrawOnIdlDone = true; +#endif + MFnDagNode nodeFn; + MDagPathArray paths; + shnodes.clear(); + eventsHappen = true; + shaveUtil::getShaveNodes(paths); //it should not trigger ::compute + for (unsigned int i = 0; i < paths.length(); i++) + { + nodeFn.setObject(paths[i].node()); + + if (nodeFn.typeId() == shaveHairShape::id) + { + shaveHairShape* shape = (shaveHairShape*)nodeFn.userNode(); + shape->dirtyDisplay(); + shnodes.push_back(shape); + } + } + ////////////////////////////////// + + shaveGlobals::Globals g; + shaveGlobals::getGlobals(g); + + mFastBrush = g.fastBrush; + + MObject manipObj; + + mActiveCtx = this; + + mManip = (shaveBrushManip*)shaveBrushManip::newManipulator( + shaveBrushManip::nodeTypeName, + manipObj + ); + + if (mManip) + { + mManip->thisObj = manipObj; + mManip->setFastBrush(mFastBrush); + + // + // %%% We should really find the current pointer position relative + // the active view and pass that to the manip as well then + // call setActive(). For now we have a kludge that the first + // call to setBrushPos() will do the setActive() call for us. + // + mManip->setBrushSize(mBrushSize); + addManipulator(manipObj); + + if (!mFastBrush) + { + // Create a dummy transform which we can connect the manip to and + // move around in response to mouse movements. + MStatus st; + MDagModifier dagMod; + MObject dummy; + + dummy = dagMod.createNode("transform", MObject::kNullObj, &st); + if (!st) + { + MGlobal::displayError( + MString("Shave: cannot create proxy transform for brush: ") + + st.errorString() + ); + } + else + { + dagMod.renameNode(dummy, "shaveBrushProxy"); + + st = dagMod.doIt(); + + if (!st) + { + MGlobal::displayError( + MString("Shave: cannot commit proxy transform for brush: ") + + st.errorString() + ); + } + else + { + MSelectionList list; + list.add("shaveBrushProxy"); + list.getDagPath(0, mProxyPath); + mManip->connectToDependNode(mProxyPath.node()); + } + } + } + } + + // + // Prior to Maya 7.0 there was no ModelPanelSetFocus event, so instead + // we watch for whenever the busy state goes false since that happens + // whenever the user pops between single-view and four-view. + // + MString eventName = "ModelPanelSetFocus"; + + mActiveViewChangedId = MEventMessage::addEventCallback( + eventName, activeViewChanged, this + ); + + catchActiveViewEvents(); + MGlobal::executeCommand("shave_prepareForBrushing"); + MGlobal::executeCommand("shaveBrush_setHotkeys on"); + + mIsActive = true; + + // + // Let the hair nodes know that a brush is active. + // + shaveHairUI::setBrushActive(true); + //for vp2.0 + mTargetShape = NULL; + { + MSelectionList selection; + MGlobal::getActiveSelectionList(selection); + + MObject comp; + bool componentMode = (MGlobal::selectionMode() + == MGlobal::kSelectComponentMode); + unsigned i; + MDagPath path; + MFnDagNode nodeFn; + + for (i = 0; i < selection.length(); i++) + { + if (selection.getDagPath(i, path, comp) && path.isValid()) + { + path.extendToShape(); + + // + // If we're in component selection mode then ignore any + // nodes which don't have components selected. + // + if (!componentMode || !comp.isNull()) + { + nodeFn.setObject(path.node()); + + if (nodeFn.typeId() == shaveHairShape::id) + { + mTargetShape = (shaveHairShape*)nodeFn.userNode(); + break; + } + } + } + } + } + if(mTargetShape) + { + mTargetShape->setBrushActive(true); + //mTargetShape->makeCurrent(); //here to avoid delays on mouse press + } +} + + +//---------------------------------------------------------------- +// +// Internal Methods +// +//---------------------------------------------------------------- + +void shaveCursorCtx::activeViewChanged(void* clientData) +{ + shaveCursorCtx* brushCtx = (shaveCursorCtx*)clientData; + + brushCtx->uncatchActiveViewEvents(); + brushCtx->catchActiveViewEvents(); +} + + +void shaveCursorCtx::catchActiveViewEvents() +{ + M3dView view = M3dView::active3dView(); + + if(qApp) + { + mv = new shaveQtMouseWatcher(this); + qApp->installEventFilter(mv);; + } + else + { + printf("shaveCursorCtx: qApp is null\n"); + } +} + + +void shaveCursorCtx::cleanupStroke() +{ + // + // Joe says that we don't need to reset the rest pose when brushing, + // thus the 'false' in the call below. + // + if(mTargetShape) + { + mTargetShape->updateParams(); + mTargetShape->applyEdits(false); + } + //unhideHair(); //we do not show/hide hair but decrese haircount + mDoingStroke = false; +} + + +MPoint shaveCursorCtx::getWorldPt( + const M3dView& view, short viewX, short viewY +) const +{ + // + // Calculate the point along the ray passing through the screen coords + // which is at the same depth, relative to the clipping planes, as + // the centroid. + // + MPoint farClip; + MPoint nearClip; + + view.viewToWorld(viewX, viewY, nearClip, farClip); + + MVector mouseRay = farClip - nearClip; + double mouseDist = mouseRay.length() * mCentroidFraction; + + mouseRay.normalize(); + + return nearClip + mouseRay * mouseDist; +} + + +void shaveCursorCtx::hideHair() +{ + // + // Completely hide all hairnodes except the target, so that they don't + // draw at all. + // + MDagPathArray paths; + + shaveUtil::getShaveNodes(paths); + mHiddenHairNodes.clear(); + + unsigned i; + + for (i = 0; i < paths.length(); i++) + { + if (paths[i].node() != mTargetShape->thisMObject()) + { + MFnDagNode nodeFn(paths[i]); + bool isVisible; + MPlug plug = nodeFn.findPlug("visibility"); + + plug.getValue(isVisible); + + if (isVisible) + { + plug.setValue(false); + mHiddenHairNodes.append(paths[i]); + } + } + } + + // + // Turn off hair display on the target. + // +#if 0 + // + // %%% This code *should* work but doesn't, the reason being that + // that setting dspyMode dirties both triggerAttr and outputMesh. + // Retrieving either one of those will call computeOutputMesh() + // which will in turn call SHAVExform, even if the current node + // is already in the engine and nothing has changed. + // + // To get around it shaveHairShape will have to be more clever + // about determining whether it needs to do an xform. In the + // meantime we use the workaround in the 'else' portion of this + // ifdef, which uses a purpose-built method for temporarily + // disabling hair display. + // + short hairDisplayMode; + MPlug plug(mTargetShape->thisMObject(), shaveHairShape::dspyMode); + + plug.getValue(hairDisplayMode); + mTargetDisplayMode = (shaveHairShape::HairDisplayMode)hairDisplayMode; + + if (mTargetDisplayMode != shaveHairShape::kHairDisplayNone) + plug.setValue((short)shaveHairShape::kHairDisplayNone); +#else + mTargetShape->enableHairDisplay(false); +#endif +} + +void shaveCursorCtx::leaveWindow(MNativeWindowHdl window, int x, int y) +{ + M3dView view = M3dView::active3dView(); + + if (window == NULL || // vlad|17Mar2010 + window == view.window()) + mManip->leaveView(); +} + + +void shaveCursorCtx::motionEventHandler(shaveCursorCtx::EventData* event) +{ + switch (event->type) + { +#if defined(_WIN32) + case kMouseLeftWindow: + mTrackingLeaveWindow = false; + case kMouseMoved: + { + M3dView activeView = M3dView::active3dView(); + HWND viewWin = activeView.window(); + HWND viewParentWin = GetParent(viewWin); + + // + // For some reason mouse events are delivered to the parent + // of the view's window. + // + if (event->window == viewWin) + { + if (event->type == kMouseLeftWindow) + leaveWindow(viewWin, event->x, event->y); + else + { + // + // Have Windows send a WM_MOUSELEAVE message when the + // pointer leaves the view. + // + if (!mTrackingLeaveWindow) + { + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = viewParentWin; + TrackMouseEvent(&tme); + mTrackingLeaveWindow = true; + } + + pointerMoved(viewWin, event->x, event->y); + } + } + } + break; +#else + case kMouseLeftWindow: + leaveWindow(event->window, event->x, event->y); + break; + + case kMouseMoved: + pointerMoved(event->window, event->x, event->y); + break; +#endif + + default: + break; + } + + delete event; +} + +void shaveCursorCtx::pointerMoved(MNativeWindowHdl window, int x, int y) +{ + //////////////////////// + //mManip->dirtyDisplay(); + //return; + //////////////////////// + M3dView view = M3dView::active3dView(); + + if (window == view.window()) + { + if (mIsResizing) + { + // + // Calculate the per-pixel scaling factor along X. + // + int rx; + int ry; + + mManip->getBrushRadii(rx, ry); + + float scalePerPixel = mBrushSize / (float)rx; + float minSize = scalePerPixel * 4.5f; + + // + // Determine what the new scaling factor must be for the radius + // to grow in the X direction by the same number of pixels as + // the mouse was dragged. + // + float newSize = mStrokeStartBrushSize + + scalePerPixel * (float)(x - mStrokeStartX); + + if (newSize < minSize) + newSize = minSize; + else if (newSize > 1.0f) + newSize = 1.0f; + + setBrushSize(newSize); + } + else + { + mManip->setBrushPos(x, view.portHeight() - 1 - y); + view.refresh(false,true); + //view.refresh(true,true); //why do we need it here? + } + } + +} + + +void shaveCursorCtx::uncatchActiveViewEvents() +{ + if(mv) + { + delete mv ; + mv = NULL; + } +} + + +void shaveCursorCtx::unhideHair() +{ + unsigned i; + + for (i = 0; i < mHiddenHairNodes.length(); i++) + { + MFnDagNode nodeFn(mHiddenHairNodes[i]); + MPlug plug = nodeFn.findPlug("visibility"); + + plug.setValue(true); + } + + mHiddenHairNodes.clear(); + + // + // Restore the target's hair display. + // +#if 0 + // + // See hideHair() for the reason for this ifdef. + // + if (mTargetDisplayMode != shaveHairShape::kHairDisplayNone) + { + MPlug plug(mTargetShape->thisMObject(), shaveHairShape::dspyMode); + plug.setValue((short)mTargetDisplayMode); + } +#else + mTargetShape->enableHairDisplay(true); +#endif +} + + +#ifdef _WIN32 +LRESULT CALLBACK shaveCursorCtx::WinEventHandler( + int nCode, WPARAM wParam, LPARAM lParam +) +{ + shaveCursorCtx* brushCtx = getActiveCtx(); + + if (brushCtx && (nCode == HC_ACTION)) + { + MSG* msgInfo = (MSG*)lParam; + int msgType = LOWORD(msgInfo->message); + + switch (msgType) + { + case WM_MOUSELEAVE: + case WM_MOUSEMOVE: + { + EventData* event = new EventData; + + if (msgType == WM_MOUSEMOVE) + event->type = kMouseMoved; + else + event->type = kMouseLeftWindow; + + event->window = msgInfo->hwnd; + + RECT rect; + GetWindowRect(event->window, &rect); + + event->x = msgInfo->pt.x - rect.left; + event->y = msgInfo->pt.y - rect.top; + + brushCtx->motionEventHandler(event); + } + break; + + default: + break; + } + } + + return CallNextHookEx(0, nCode, wParam, lParam); +} +#endif + + +#ifdef LINUX +void shaveCursorCtx::XEventHandler( + Widget widget, + XtPointer clientData, + XEvent* event, + Boolean* continueDispatch) +{ + EventData* eventData = new EventData; + + switch (event->type) + { + case LeaveNotify: + eventData->type = kMouseLeftWindow; + eventData->window = event->xcrossing.window; + eventData->x = event->xcrossing.x; + eventData->y = event->xcrossing.y; + break; + + case MotionNotify: + eventData->type = kMouseMoved; + eventData->window = event->xmotion.window; + eventData->x = event->xmotion.x; + eventData->y = event->xmotion.y; + break; + + default: + delete eventData; + return; + } + + shaveCursorCtx* brushCtx = (shaveCursorCtx*)clientData; + + brushCtx->motionEventHandler(eventData); +} +#endif |