// Shave and a Haircut // (c) 2019 Epic Games // US Patent 6720962 //Qt headers must be included before any others !!! #if (defined LINUX) || (defined OSMac_) #include #include #if QT_VERSION < 0x050000 # include #else # include #endif #endif #include #include #include #include #include #include #include #include #include #include #include #if defined(_WIN32) && (MAYA_API_VERSION >= 201600) #include #endif #ifdef OSMac_MachO_ # include # include //# include #else //#define GL_GLEXT_PROTOTYPES # include # include #endif #include "shaveBrushManip.h" MTypeId shaveBrushManip::id(0x001029B8); MString shaveBrushManip::nodeTypeName = "shaveBrushManip"; // The 'fast brush' does an XOR draw on top of the view, meaning that it // can be drawn and erased without having Maya redraw the entire view. // // The standard brush is drawn normally during a refresh, which is // triggered either by moving a proxy transform within the scene to match // the mouse movement, or by forcing a refresh. // // The fast brush is enabled by default in shaveGlobals, but on some // graphics cards it can cause visual anomalies, in which case the user // can switch to the standard brush. shaveBrushManip::shaveBrushManip() : mBrushSize(0.0f) , mClearOld(false) , mCX(-1) , mCY(-1) , mFastBrush(true) , mIsActive(false) , mViewHeight(0) , mViewWidth(0) , mWindow(0) { aWidget = NULL; } shaveBrushManip::~shaveBrushManip() { mProxy = MObject::kNullObj; } MStatus shaveBrushManip::connectToDependNode(const MObject& node) { //thisObj = thisMObject(); finishAddingManips(); MPxManipContainer::connectToDependNode(node); if (!mFastBrush) { MFnDependencyNode nodeFn(node); mProxy = node; } return MS::kSuccess; } MStatus shaveBrushManip::createChildren() { // // We don't want any manips because their handles, even though not // drawn, can still capture mouse clicks. // return MS::kSuccess; } void shaveBrushManip::draw( M3dView& view, const MDagPath& path, M3dView::DisplayStyle dStyle, M3dView::DisplayStatus dStatus ) { if (mFastBrush) { // // If Maya is asking us to redraw in the same view where we last did a // draw, then it must have already cleared the old image, so we should // not try to clear it ourselves. // if (mClearOld && isSameView(view)) mClearOld = false; } else { M3dView activeView = M3dView::active3dView(); if ((view.window() == activeView.window()) && (mCX >= 0) && (mCY >= 0) && (mCX < view.portWidth()) && (mCY < view.portHeight())) { prepareView(activeView); drawBrush(); restoreView(activeView); } } } void shaveBrushManip::getBrushRadii(int& rx, int& ry) const { rx = mRX; ry = mRY; } void shaveBrushManip::leaveView() { //MGlobal::displayInfo("leaveView"); /////////////////////////// //return; /////////////////////////// if (mIsActive) { M3dView activeView = M3dView::active3dView(); if (mFastBrush) eraseBrush(activeView); else { if(mCX != -1 || mCY != -1) //to avoid exra redraws { // This will let us know not to draw the brush. mCX = mCY = -1; // Force Maya to redraw the view so that the brush will be // erased. activeView.refresh(false, true); } } } } void shaveBrushManip::setActive(bool active) { /////////////////////////// //return; /////////////////////////// if (mIsActive != active) { // // If we're being made active then clear out the old view // information. // // If we're being made inactive then erase the current brush image. // if (active) { mWindow = 0; mViewHeight = 0; mViewWidth = 0; } else { M3dView activeView = M3dView::active3dView(); eraseBrush(activeView, false); } mIsActive = active; } } void shaveBrushManip::setBrushPos( int cx, int cy, bool forceRedraw, bool isMayaDraw ) { /////////////////////////// //return; /////////////////////////// //printf("set pos %i %i\n",cx,cx);fflush(stdout); M3dView activeView = M3dView::active3dView(); bool isNewView = !isSameView(activeView); // If someone is giving us brush positions, then we must be active. setActive(true); if (mFastBrush) { // If we were previously inactive or the view has changed, then we // will need to redraw the brush even if its position is unchanged. if (isNewView || !mIsActive) forceRedraw = true; // If the brush has moved, or this is a forced redraw, then do the // draw. if (forceRedraw || (mCX != cx) || (mCY != cy)) { bool activeViewPrepared = eraseBrush(activeView, true); if (!activeViewPrepared) prepareView(activeView); // If we've changed views, or the view has changed size, then // we need to recalculate the radii. if (isNewView) { saveView(activeView); calculateRadii(); } mCX = cx; mCY = cy; if ((mCX >= 0) && (mCY >= 0)) { drawBrush(); mClearOld = true; } if (!isMayaDraw) swapGLBuffers(activeView); restoreView(activeView); } } else { // If the brush has moved, move the proxy transform // correspondingly. if ((mCX != cx) || (mCY != cy)) { // If we've changed views, or the view has changed size, then // we need to recalculate the radii. if (isNewView) { saveView(activeView); calculateRadii(); } MPoint nearPlane; MPoint farPlane; activeView.viewToWorld(cx, cy, nearPlane, farPlane); MVector translation( (nearPlane.x + farPlane.x) / 2.0, (nearPlane.y + farPlane.y) / 2.0, (nearPlane.z + farPlane.z) / 2.0 ); MFnTransform transformFn(mProxy); transformFn.setTranslation(translation, MSpace::kObject); mCX = cx; mCY = cy; } } MHWRender::MRenderer::setGeometryDrawDirty(thisObj); } void shaveBrushManip::setBrushSize(float brushSize) { /////////////////////////// //return; /////////////////////////// if (brushSize != mBrushSize) { M3dView view; bool viewPrepared = false; bool foundView = findOldView(view); if (foundView) viewPrepared = eraseBrush(view, true); mBrushSize = brushSize; if (foundView) { calculateRadii(); if (!viewPrepared) prepareView(view); if ((mCX >= 0) && (mCY >= 0)) { drawBrush(); mClearOld = true; } swapGLBuffers(view); restoreView(view); } } } //---------------------------------------------------------------------- // // Internal Methods // //---------------------------------------------------------------------- void shaveBrushManip::calculateRadii() { unsigned radius; if (mViewHeight < mViewWidth) radius = (unsigned)((float)mViewHeight * mBrushSize / 2.0f + 0.5); else radius = (unsigned)((float)mViewWidth * mBrushSize / 2.0f + 0.5); if (radius < 4) radius = 4; // // %%% We eventually need to compensate for non-square pixels by // assigning X and Y different radii. // mRX = radius; mRY = radius; } void shaveBrushManip::drawBrush() const { //printf("draw pos %i %i\n",mCX,mCY);fflush(stdout); // // Draw the outer circle. We use an ellipse drawing method because // eventually we want to support displays with non-square pixels, which // will require different X and Y radii. // drawEllipse(mCX, mCY, mRX, mRY); #if 0 // // Draw a small crosshair in the center. // glBegin(GL_LINES); glVertex2i(mCX-4, mCY); glVertex2i(mCX+4, mCY); glVertex2i(mCX, mCY-4); glVertex2i(mCX, mCY+4); glEnd(); #endif } // // This method uses a DDA to draw an ellipse with the given radii into the // bitmap. // // The algorithm is taken almost verbatim from Tim Kientzle's 'Algorithm // Alley' column in the July, 1994 issue of Dr. Dobb's Journal. // void shaveBrushManip::drawEllipse(int cx, int cy, unsigned rx, unsigned ry) { const unsigned rxSquared = rx * rx; const unsigned rySquared = ry * ry; const unsigned twoRXSquared = 2 * rxSquared; const unsigned twoRYSquared = 2 * rySquared; glBegin(GL_POINTS); // // Plot the octant from the top of the ellipse to the top-right // and reflect it in the other four quadrants. // int x = 0; int y = ry; int twoXTimesRYSquared = 0; int twoYTimesRXSquared = (int)(y * twoRXSquared); int error = -(int)(y * rxSquared); while (twoXTimesRYSquared <= twoYTimesRXSquared) { drawMirroredVert(cx, cy, x, y); x += 1; twoXTimesRYSquared += (int)twoRYSquared; error += twoXTimesRYSquared - (int)rySquared; if (error >= 0) { y -= 1; twoYTimesRXSquared -= (int)twoRXSquared; error -= twoYTimesRXSquared; } } // // Plot the octant from the right of the ellipse to the top-right // and reflect it in the other four quadrants. // x = rx; y = 0; twoXTimesRYSquared = (int)(x * twoRYSquared); twoYTimesRXSquared = 0; error = -(int)(x * rySquared); while (twoXTimesRYSquared > twoYTimesRXSquared) { drawMirroredVert(cx, cy, x, y); y += 1; twoYTimesRXSquared += (int)twoRXSquared; error += twoYTimesRXSquared - (int)rxSquared; if (error >= 0) { x -= 1; twoXTimesRYSquared -= (int)twoRYSquared; error -= twoXTimesRYSquared; } } glEnd(); } void shaveBrushManip::drawMirroredVert(int cx, int cy, int x, int y) { // // Mirror the vert in the four quadrants around the center point. // // Because we are drawing in XOR mode, we don't want to plot the same // point an even number of times as that will be as if we hadn't // plotted it at all. So we have special cases for x==0 and y==0 to // prevent that. // if (x == 0) { glVertex2i(cx, cy + y); glVertex2i(cx, cy - y); } else if (y == 0) { glVertex2i(cx + x, cy); glVertex2i(cx - x, cy); } else { glVertex2i(cx + x, cy + y); glVertex2i(cx + x, cy - y); glVertex2i(cx - x, cy + y); glVertex2i(cx - x, cy - y); } } bool shaveBrushManip::eraseBrush( M3dView& currentView, bool dontRestoreCurrentView ) { bool currentViewIsPrepared = false; if (mFastBrush && mClearOld) { // // If the previous brush image was drawn in a different view from // the one passed to us by the caller, then we must find that view. // if (!isSameView(currentView)) { M3dView oldView; if (findOldView(oldView)) { prepareView(oldView); drawBrush(); swapGLBuffers(oldView); restoreView(oldView); } } else { prepareView(currentView); drawBrush(); if (dontRestoreCurrentView) currentViewIsPrepared = true; else { swapGLBuffers(currentView); restoreView(currentView); } } mClearOld = false; } return currentViewIsPrepared; } bool shaveBrushManip::findOldView(M3dView& view) const { if (mIsActive) { unsigned numViews = M3dView::numberOf3dViews(); unsigned i; for (i = 0; i < numViews; i++) { M3dView::get3dView(i, view); if (isSameView(view)) return true; } } return false; } bool shaveBrushManip::isSameView(M3dView& view) const { if ((view.window() != mWindow) || (view.portHeight() != mViewHeight) || (view.portWidth() != mViewWidth)) { return false; } #if OSMac_ QWidget* wgt = view.widget(); return (wgt == aWidget); #endif return true; } void shaveBrushManip::prepareView(M3dView& view) { view.beginGL(); glPushAttrib(GL_TRANSFORM_BIT | GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D( 0.0, (GLdouble)view.portWidth(), 0.0, (GLdouble)view.portHeight() ); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); // This bit of magic is from marqueeTool.cpp in the devkit. glTranslatef(0.375, 0.375, 0.0); glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); if (mFastBrush) { glEnable(GL_COLOR_LOGIC_OP); glLogicOp(GL_XOR); // Because we're doing XOR, if we set the draw color to 12 (red) // it won't appear red when over the default grey background of // Maya's views. By choosing light green XOR will give us // reddish-orange against a grey background, which is at least // close to red. view.setDrawColor(18, M3dView::kActiveColors); } else view.setDrawColor(12, M3dView::kActiveColors); // red } void shaveBrushManip::restoreView(M3dView& view) { glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glPopAttrib(); view.endGL(); } void shaveBrushManip::saveView(M3dView& view) { mWindow = view.window(); mViewHeight = view.portHeight(); mViewWidth = view.portWidth(); #ifdef OSMac_ // // On OSX the views do not have their own windows but are merely // regions within the main window. So to know if we have the correct // view we must compare not just the window but the position of the // region. // aWidget = view.widget(); #endif } void shaveBrushManip::swapGLBuffers(M3dView& view) { #if defined(LINUX) glXSwapBuffers(view.display(), view.window()); #elif defined(OSMac_) //there is no good way to swap buffers now //so we grey out 'fast brush' checkbox for 2011 //const QGLContext* qgl = QGLContext::currentContext(); //if(qgl) // qgl->swapBuffers(); //else // MGlobal::displayInfo("null QGLContext"); //NSOpenGLContext* nsgl = view.display(); //[nsgl flushBuffer]; //cocoa //nsgl->flushBuffer(); //CGLContextObj* cgl = nsgl->CGLContextObj(); #elif defined(_WIN32) SwapBuffers(view.deviceContext()); #else #error Unsupported OS type. #endif } MString shaveBrushManip::drawDbClassification("drawdb/geometry/shaveBrushManip"); MString shaveBrushManip::drawRegistrantId("shave_and_haircut_brush"); MObject shaveBrushManip::trigger; MStatus shaveBrushManip::initialize() { MStatus stat; MFnNumericAttribute na; trigger = na.create("atrigger", "tr", MFnNumericData::kInt,0,&stat); if(stat != MStatus::kSuccess) { MGlobal::displayError("shaveBrushManip: can't create attribute"); } na.setStorable(false); na.setHidden(true); stat = addAttribute(trigger); if(stat != MStatus::kSuccess) { MGlobal::displayError("shaveBrushManip: can't add attribute"); } return MStatus::kSuccess; //return MPxManipContainer::initialize(); } // pre-draw callback static void shaveBrushPreDrawCallback( MHWRender::MDrawContext& context, const MHWRender::MRenderItemList& renderItemList, MHWRender::MShaderInstance *shaderInstance ) { //printf("Pre-draw callback triggered for render item with name '%s'\n", renderItem->name().asChar()); } // post-draw callback static void shaveBrushPostDrawCallback( MHWRender::MDrawContext& context, const MHWRender::MRenderItemList& renderItemList, MHWRender::MShaderInstance *shaderInstance ) { //printf("Post-draw callback triggered for render item with name '%s'\n", renderItem->name().asChar()); } shaveBrushOverride::shaveBrushOverride(const MObject& obj) : MPxGeometryOverride(obj) , brush(NULL) { MStatus status; MFnDependencyNode node(obj, &status); if (status) { brush = dynamic_cast(node.userNode()); } } shaveBrushOverride::~shaveBrushOverride() { } MHWRender::DrawAPI shaveBrushOverride::supportedDrawAPIs() const { //return MHWRender::kAllDevices; return MHWRender::kOpenGL; } void shaveBrushOverride::updateDG() { if (brush) { //nothing to do at the moment } } //#define BRUSH_BY_SHADER //geom shaders are static const bool debugShader = false;//true; void shaveBrushOverride::updateRenderItems(const MDagPath& path, MHWRender::MRenderItemList& list) { MHWRender::MRenderer* renderer = MHWRender::MRenderer::theRenderer(); if (!renderer) return; const MHWRender::MShaderManager* shaderMgr = renderer->getShaderManager(); if (!shaderMgr) return; MHWRender::MRenderItem* item = NULL; int index = list.indexOf( "shaveBrush", //#ifdef BRUSH_BY_SHADER // MHWRender::MGeometry::kPoints, //#else MHWRender::MGeometry::kLines, //#endif MHWRender::MGeometry::kAll); if (index < 0) { item = MHWRender::MRenderItem::Create ( "shaveBrush", //#ifdef BRUSH_BY_SHADER // MHWRender::MGeometry::kPoints, //#else MHWRender::MGeometry::kLines, //#endif MHWRender::MGeometry::kAll, false); list.append(item); //MHWRender::MShaderInstance* shader = shaderMgr->getStockShader( // MHWRender::MShaderManager::k3dSolidShader, // debugShader ? shaveBrushPreDrawCallback : NULL, // debugShader ? shaveBrushPostDrawCallback : NULL); MHWRender::MShaderInstance* shader = shaderMgr->getEffectsFileShader("shaveHair.cgfx","Brush"); if (shader) { //static const float theColor[] = {0.0f, 0.01f, 0.27f, 1.0f}; //shader->setParameter("solidColor", theColor); // assign shader item->setShader(shader); // sample debug code if (debugShader) { MStringArray params; shader->parameterList(params); unsigned int numParams = params.length(); printf("DEBUGGING SHADER, BEGIN PARAM LIST OF LENGTH %d\n", numParams); for (unsigned int i=0; iisArrayParameter(params[i]) ? "YES" : "NO"); } printf("END PARAM LIST\n"); fflush(stdout); } } } } void shaveBrushOverride::populateGeometry( const MHWRender::MGeometryRequirements& requirements, const MHWRender::MRenderItemList& itemList, MHWRender::MGeometry& data ) { MHWRender::MVertexBuffer* positionBuffer = NULL; float* positions = NULL; const MHWRender::MVertexBufferDescriptorList& descList = requirements.vertexRequirements(); int numVertexReqs = descList.length(); MHWRender::MVertexBufferDescriptor desc; #ifndef BRUSH_BY_SHADER int nsegs = 24; int nverts = nsegs*2; #endif for (int reqNum=0; reqNumacquire(2); #else positions = (float*)positionBuffer->acquire(nverts); #endif } break; case MHWRender::MGeometry::kColor: case MHWRender::MGeometry::kTexture: case MHWRender::MGeometry::kNormal: case MHWRender::MGeometry::kTangent: case MHWRender::MGeometry::kBitangent: default: break; } } if (positions) { #ifdef BRUSH_BY_SHADER M3dView activeView = M3dView::active3dView(); float H = (float)activeView.portHeight(); float W = (float)activeView.portWidth(); float x = (brush->mCX - 0.5f*W)/(0.5f*W); float y = (brush->mCY - 0.5f*H)/(0.5f*H); //MVector C(0.0f, 0.0f, 0.0f); MVector C(x, y, 0.0f); //MVector C(brush->mCX, brush->mCY, 0.0f); positions[0] = C.x; positions[1] = C.y; positions[2] = 0.0f; float w = brush->mRX/(0.5f*W); float h = brush->mRY/(0.5f*H); MVector P(w, h, 0.0f); //MVector P(brush->mRX, brush->mRY, 0.0f); positions[3] = P.x; positions[4] = P.y; positions[5] = 0.0f; #else float R = 0.3f; M3dView activeView = M3dView::active3dView(); float H = (float)activeView.portHeight(); float W = (float)activeView.portWidth(); float x = (brush->mCX - 0.5f*W)/(0.5f*W); float y = (brush->mCY - 0.5f*H)/(0.5f*H); MVector C(x, y, 0.0f); float _w = brush->mRX/(0.5f*W); float _h = brush->mRY/(0.5f*H); MVector P(_w, _h, 0.0f); const float pi = 3.14159f; float dstep = 360.0f/nsegs; float rstep = dstep*pi/(180.0f); int pid = 0; for (int h = 0; h < nsegs; h++) { int hh = (h+1)%nsegs; float a1 = rstep*(float)h; float a2 = rstep*(float)hh; //float x1 = C.x + R*sin(a1); //float y1 = C.y + R*cos(a1); //float x2 = C.x + R*sin(a2); //float y2 = C.y + R*cos(a2); float x1 = (float)(C.x + P.x*sin(a1)); float y1 = (float)(C.y + P.y*cos(a1)); float x2 = (float)(C.x + P.x*sin(a2)); float y2 = (float)(C.y + P.y*cos(a2)); positions[pid++] = x1; positions[pid++] = y1; positions[pid++] = 0.0f; positions[pid++] = x2; positions[pid++] = y2; positions[pid++] = 0.0f; } #endif positionBuffer->commit(positions); } // index data MHWRender::MIndexBuffer* indexBuffer = NULL; int numItems = itemList.length(); for (int i=0; irequiredVertexBuffers(); int numBufs = itemBuffers.length(); MHWRender::MVertexBufferDescriptor desc; for (int bufNum=0; bufNumname().asChar()); printf("\tBufferName: %s\n", desc.name().asChar()); printf("\tDataType: %s (dimension %d)\n", MHWRender::MGeometry::dataTypeString(desc.dataType()).asChar(), desc.dimension()); printf("\tSemantic: %s\n", MHWRender::MGeometry::semanticString(desc.semantic()).asChar()); printf("\n"); } } fflush(stdout); } //same indexing for all if (item->name() == "shaveBrush") { if (!indexBuffer) { indexBuffer = data.createIndexBuffer(MHWRender::MGeometry::kUnsignedInt32); if (indexBuffer) { #ifdef BRUSH_BY_SHADER unsigned int* buffer = (unsigned int*)indexBuffer->acquire(2); if (buffer) { buffer[0] = 0; buffer[1] = 1; indexBuffer->commit(buffer); } #else unsigned int* buffer = (unsigned int*)indexBuffer->acquire(nsegs*2); if (buffer) { int idx = 0; for (int h = 0; h < nsegs; h++) { buffer[idx] = idx; buffer[idx+1] = idx+1; idx += 2; } indexBuffer->commit(buffer); } #endif } } // Associate same index buffer with either render item if (indexBuffer) { item->associateWithIndexBuffer(indexBuffer); } } } } void shaveBrushOverride::cleanUp() { }