// Shave and a Haircut // (c) 2019 Epic Games // US Patent 6720962 //Qt headers must be included before any others !!! #include #include #include #include #if QT_VERSION < 0x050000 # include # include #else # include # include #endif #include #include using namespace std; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 // // 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 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(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