static void createMesh(ShapeDesc const & shapeDesc, int level) { typedef Far::LimitStencilTableFactory::LocationArray LocationArray; Shape const * shape = Shape::parseObj(shapeDesc.data.c_str(), shapeDesc.scheme); // create Far mesh (topology) OpenSubdiv::Sdc::SchemeType sdctype = GetSdcType(*shape); OpenSubdiv::Sdc::Options sdcoptions = GetSdcOptions(*shape); OpenSubdiv::Far::TopologyRefiner * refiner = OpenSubdiv::Far::TopologyRefinerFactory<Shape>::Create(*shape, OpenSubdiv::Far::TopologyRefinerFactory<Shape>::Options(sdctype, sdcoptions)); // save coarse topology (used for coarse mesh drawing) OpenSubdiv::Far::TopologyLevel const & refBaseLevel = refiner->GetLevel(0); g_controlMeshDisplay.SetTopology(refBaseLevel); int nverts = refBaseLevel.GetNumVertices(); // save rest pose g_orgPositions = shape->verts; if (g_bilinear) { Far::TopologyRefiner::UniformOptions options(level); options.fullTopologyInLastLevel = true; refiner->RefineUniform(options); } else { Far::TopologyRefiner::AdaptiveOptions options(level); options.useSingleCreasePatch = false; refiner->RefineAdaptive(options); } Far::PtexIndices ptexIndices(*refiner); int nfaces = ptexIndices.GetNumFaces(); float * u = new float[g_nsamples*nfaces], * uPtr = u, * v = new float[g_nsamples*nfaces], * vPtr = v; std::vector<LocationArray> locs(nfaces); srand( static_cast<int>(2147483647) ); // use a large Pell prime number for (int face=0; face<nfaces; ++face) { LocationArray & larray = locs[face]; larray.ptexIdx = face; larray.numLocations = g_nsamples; larray.s = uPtr; larray.t = vPtr; for (int j=0; j<g_nsamples; ++j, ++uPtr, ++vPtr) { *uPtr = (float)rand()/(float)RAND_MAX; *vPtr = (float)rand()/(float)RAND_MAX; } } delete g_controlStencils; g_controlStencils = Far::LimitStencilTableFactory::Create(*refiner, locs); delete [] u; delete [] v; g_nsamplesDrawn = g_controlStencils->GetNumStencils(); delete shape; delete refiner; delete g_stencilOutput; if (g_kernel == kCPU) { g_stencilOutput = new StencilOutput<Osd::CpuGLVertexBuffer, Osd::CpuGLVertexBuffer, Far::LimitStencilTable, Osd::CpuEvaluator>( g_controlStencils, nverts); #ifdef OPENSUBDIV_HAS_OPENMP } else if (g_kernel == kOPENMP) { g_stencilOutput = new StencilOutput<Osd::CpuGLVertexBuffer, Osd::CpuGLVertexBuffer, Far::LimitStencilTable, Osd::OmpEvaluator>( g_controlStencils, nverts); #endif #ifdef OPENSUBDIV_HAS_TBB } else if (g_kernel == kTBB) { g_stencilOutput = new StencilOutput<Osd::CpuGLVertexBuffer, Osd::CpuGLVertexBuffer, Far::LimitStencilTable, Osd::TbbEvaluator>( g_controlStencils, nverts); #endif #ifdef OPENSUBDIV_HAS_CUDA } else if (g_kernel == kCUDA) { g_stencilOutput = new StencilOutput<Osd::CudaGLVertexBuffer, Osd::CudaGLVertexBuffer, Osd::CudaStencilTable, Osd::CudaEvaluator>( g_controlStencils, nverts); #endif #ifdef OPENSUBDIV_HAS_OPENCL } else if (g_kernel == kCL) { static Osd::EvaluatorCacheT<Osd::CLEvaluator> clEvaluatorCache; g_stencilOutput = new StencilOutput<Osd::CLGLVertexBuffer, Osd::CLGLVertexBuffer, Osd::CLStencilTable, Osd::CLEvaluator, CLDeviceContext>( g_controlStencils, nverts, &clEvaluatorCache, &g_clDeviceContext); #endif #ifdef OPENSUBDIV_HAS_GLSL_TRANSFORM_FEEDBACK } else if (g_kernel == kGLXFB) { static Osd::EvaluatorCacheT<Osd::GLXFBEvaluator> glXFBEvaluatorCache; g_stencilOutput = new StencilOutput<Osd::GLVertexBuffer, Osd::GLVertexBuffer, Osd::GLStencilTableTBO, Osd::GLXFBEvaluator>( g_controlStencils, nverts, &glXFBEvaluatorCache); #endif #ifdef OPENSUBDIV_HAS_GLSL_COMPUTE } else if (g_kernel == kGLCompute) { static Osd::EvaluatorCacheT<Osd::GLComputeEvaluator> glComptueEvaluatorCache; g_stencilOutput = new StencilOutput<Osd::GLVertexBuffer, Osd::GLVertexBuffer, Osd::GLStencilTableSSBO, Osd::GLComputeEvaluator>( g_controlStencils, nverts, &glComptueEvaluatorCache); #endif } updateGeom(); }
MStatus MayaPolySmooth::compute( const MPlug& plug, MDataBlock& data ) { MStatus status; // Check which output attribute we have been asked to compute. If this // node doesn't know how to compute it, we must return // MS::kUnknownParameter. // if( plug == a_output ) { bool createdSubdMesh = false; int subdivisionLevel = data.inputValue(a_subdivisionLevels).asInt(); short stateH = data.inputValue(state).asShort(); if ((subdivisionLevel > 0) and (stateH !=1)) { // == Retrieve input mesh ==================================== // Get attr values MObject inMeshObj = data.inputValue(a_inputPolymesh).asMesh(); short vertBoundaryMethod = data.inputValue(a_vertBoundaryMethod).asShort(); short fvarBoundaryMethod = data.inputValue(a_fvarBoundaryMethod).asShort(); bool fvarPropCorners = data.inputValue(a_fvarPropagateCorners).asBool(); bool smoothTriangles = data.inputValue(a_smoothTriangles).asBool(); short creaseMethodVal = data.inputValue(a_creaseMethod).asShort(); // == Get Mesh Functions and Iterators ========================== MFnMeshData inMeshDat(inMeshObj); MFnMesh inMeshFn(inMeshObj, &status); MCHECKERR(status, "ERROR getting inMeshFn\n"); MItMeshPolygon inMeshItPolygon(inMeshObj, &status); MCHECKERR(status, "ERROR getting inMeshItPolygon\n"); // Convert attr values to OSD enums OpenSubdiv::Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK; // == Create Far topology ========================== OpenSubdiv::Sdc::Options options; options.SetVtxBoundaryInterpolation(ConvertMayaVtxBoundary(vertBoundaryMethod)); options.SetFVarLinearInterpolation(ConvertMayaFVarBoundary(fvarBoundaryMethod, fvarPropCorners)); options.SetCreasingMethod(creaseMethodVal ? OpenSubdiv::Sdc::Options::CREASE_CHAIKIN : OpenSubdiv::Sdc::Options::CREASE_UNIFORM); options.SetTriangleSubdivision(smoothTriangles ? OpenSubdiv::Sdc::Options::TRI_SUB_SMOOTH : OpenSubdiv::Sdc::Options::TRI_SUB_CATMARK); // Storage for face-varying values (UV sets, vertex colors...) std::vector<MFloatArray> uvSet_uCoords; std::vector<MFloatArray> uvSet_vCoords; std::vector<MColorArray> colorSet_colors; bool hasUVs = false, hasColors = false; float maxCreaseSharpness=0.0f; OpenSubdiv::Far::TopologyRefiner * refiner = gatherTopology( inMeshFn, inMeshItPolygon, type, options, &hasUVs, &hasColors, uvSet_uCoords, uvSet_vCoords, colorSet_colors, &maxCreaseSharpness); assert(refiner); // == Refine & Interpolate ========================== refiner->RefineUniform(OpenSubdiv::Far::TopologyRefiner::UniformOptions(subdivisionLevel)); // Prepare vertex information Vertex const * initialVerts = reinterpret_cast<Vertex const *>(inMeshFn.getRawPoints(&status)); std::vector<Vertex> refinedVerts( refiner->GetNumVerticesTotal() - refiner->GetLevel(0).GetNumVertices()); Vertex const * srcVerts = &initialVerts[0]; Vertex * dstVerts = &refinedVerts[0]; // Verify the refiner has the correct number of values // needed to interpolate the different channels int numInitialUVs = refiner->GetLevel(0).GetNumFVarValues(CHANNELUV); int numInitialColors = refiner->GetLevel(0).GetNumFVarValues(CHANNELCOLOR); if (hasUVs && numInitialUVs <= 0) { hasUVs = false; MGlobal::displayError("Model with incorrect data, the UV channel will not be interpolated."); } if (hasColors && numInitialColors <= 0) { hasColors = false; MGlobal::displayError("Model with incorrect data, the color channel will not be interpolated."); } // Prepare UV information if needed std::vector<FVarVertexUV> initialUVs, refinedUVs; FVarVertexUV const * srcUV = NULL; FVarVertexUV * dstUV = NULL; if(hasUVs) { initialUVs.resize(numInitialUVs); refinedUVs.resize(refiner->GetNumFVarValuesTotal(CHANNELUV)); for (int i=0; i<numInitialUVs; ++i) { initialUVs[i].u = uvSet_uCoords[0][i]; initialUVs[i].v = uvSet_vCoords[0][i]; } srcUV = &initialUVs[0]; dstUV = &refinedUVs[0]; } // Prepare color information if needed std::vector<FVarVertexColor> initialColors, refinedColors; FVarVertexColor const * srcColor = NULL; FVarVertexColor * dstColor = NULL; if(hasColors) { initialColors.resize(numInitialColors); refinedColors.resize(refiner->GetNumFVarValuesTotal(CHANNELCOLOR)); for (int i=0; i<numInitialColors; ++i) { initialColors[i].r = colorSet_colors[0][i].r; initialColors[i].g = colorSet_colors[0][i].g; initialColors[i].b = colorSet_colors[0][i].b; initialColors[i].a = colorSet_colors[0][i].a; } srcColor = &initialColors[0]; dstColor = &refinedColors[0]; } // Interpolate the vertices and the different channels OpenSubdiv::Far::PrimvarRefiner primvarRefiner(*refiner); for (int level = 1; level <= subdivisionLevel; ++level) { // Interpolate vertices primvarRefiner.Interpolate(level, srcVerts, dstVerts); srcVerts = dstVerts; dstVerts += refiner->GetLevel(level).GetNumVertices(); // Interpolate the uv set if(hasUVs) { primvarRefiner.InterpolateFaceVarying(level, srcUV, dstUV, CHANNELUV); srcUV = dstUV; dstUV += refiner->GetLevel(level).GetNumFVarValues(CHANNELUV); } // Interpolate any color set if(hasColors) { primvarRefiner.InterpolateFaceVarying(level, srcColor, dstColor, CHANNELCOLOR); srcColor = dstColor; dstColor += refiner->GetLevel(level).GetNumFVarValues(CHANNELCOLOR); } } // == Convert subdivided OpenSubdiv mesh to MFnMesh Data outputMesh ============= // Create New Mesh Data Object MFnMeshData newMeshData; MObject newMeshDataObj = newMeshData.create(&status); MCHECKERR(status, "ERROR creating outputData"); // Create out mesh status = convertToMayaMeshData(*refiner, refinedVerts, hasUVs, refinedUVs, hasColors, refinedColors, inMeshFn, newMeshDataObj); MCHECKERR(status, "ERROR convertOsdFarToMayaMesh"); // Propagate objectGroups from inMesh to outMesh (for per-facet shading, etc) status = createSmoothMesh_objectGroups(inMeshFn, inMeshDat, newMeshData, subdivisionLevel, refiner->GetLevel(subdivisionLevel).GetNumFaces()); // Write to output plug MDataHandle outMeshH = data.outputValue(a_output, &status); MCHECKERR(status, "ERROR getting polygon data handle\n"); outMeshH.set(newMeshDataObj); int isolation = std::min(10,(int)ceil(maxCreaseSharpness)+1); data.outputValue(a_recommendedIsolation).set(isolation); // == Cleanup OSD ============================================ // REVISIT: Re-add these deletes delete refiner; // note that the subd mesh was created (see the section below if !createdSubdMesh) createdSubdMesh = true; } // Pass-through inMesh to outMesh if not created the subd mesh if (!createdSubdMesh) { MDataHandle outMeshH = data.outputValue(a_output, &status); status = outMeshH.copy(data.outputValue(a_inputPolymesh, &status)); MCHECKERR(status, "ERROR getting polygon data handle\n"); } // Clean up Maya Plugs data.setClean(plug); } else { // Unhandled parameter in this compute function, so return MS::kUnknownParameter // so it is handled in a parent compute() function. return MS::kUnknownParameter; } return MS::kSuccess; }
static void createMesh(ShapeDesc const & shapeDesc, int level) { typedef Far::ConstIndexArray IndexArray; typedef Far::LimitStencilTablesFactory::LocationArray LocationArray; Shape const * shape = Shape::parseObj(shapeDesc.data.c_str(), shapeDesc.scheme); // create Vtr mesh (topology) OpenSubdiv::Sdc::SchemeType sdctype = GetSdcType(*shape); OpenSubdiv::Sdc::Options sdcoptions = GetSdcOptions(*shape); OpenSubdiv::Far::TopologyRefiner * refiner = OpenSubdiv::Far::TopologyRefinerFactory<Shape>::Create(*shape, OpenSubdiv::Far::TopologyRefinerFactory<Shape>::Options(sdctype, sdcoptions)); // save coarse topology (used for coarse mesh drawing) int nedges = refiner->GetNumEdges(0), nverts = refiner->GetNumVertices(0); g_coarseEdges.resize(nedges*2); g_coarseEdgeSharpness.resize(nedges); g_coarseVertexSharpness.resize(nverts); for(int i=0; i<nedges; ++i) { IndexArray verts = refiner->GetEdgeVertices(0, i); g_coarseEdges[i*2 ]=verts[0]; g_coarseEdges[i*2+1]=verts[1]; g_coarseEdgeSharpness[i]=refiner->GetEdgeSharpness(0, i); } for(int i=0; i<nverts; ++i) { g_coarseVertexSharpness[i]=refiner->GetVertexSharpness(0, i); } g_orgPositions=shape->verts; if (g_bilinear) { Far::TopologyRefiner::UniformOptions options(level); options.fullTopologyInLastLevel = true; refiner->RefineUniform(options); } else { Far::TopologyRefiner::AdaptiveOptions options(level); options.fullTopologyInLastLevel = false; options.useSingleCreasePatch = false; refiner->RefineAdaptive(options); } int nfaces = refiner->GetNumPtexFaces(); float * u = new float[g_nsamples*nfaces], * uPtr = u, * v = new float[g_nsamples*nfaces], * vPtr = v; std::vector<LocationArray> locs(nfaces); srand( static_cast<int>(2147483647) ); // use a large Pell prime number for (int face=0; face<nfaces; ++face) { LocationArray & larray = locs[face]; larray.ptexIdx = face; larray.numLocations = g_nsamples; larray.s = uPtr; larray.t = vPtr; for (int j=0; j<g_nsamples; ++j, ++uPtr, ++vPtr) { *uPtr = (float)rand()/(float)RAND_MAX; *vPtr = (float)rand()/(float)RAND_MAX; } } delete g_controlStencils; g_controlStencils = Far::LimitStencilTablesFactory::Create(*refiner, locs); delete [] u; delete [] v; g_nsamplesDrawn = g_controlStencils->GetNumStencils(); // Create control vertex buffer (layout: [ P(xyz) ] ) delete g_controlValues; g_controlValues = Osd::CpuVertexBuffer::Create(3, nverts); // Create eval context & data buffers delete g_evalCtx; g_evalCtx = Osd::CpuEvalStencilsContext::Create(g_controlStencils); delete g_stencilValues; g_stencilValues = Osd::CpuGLVertexBuffer::Create(3, g_controlStencils->GetNumStencils() * 6 ); delete shape; delete refiner; updateGeom(); // Bind g_stencilValues as GL_LINES VAO glBindVertexArray(g_stencilsVAO); glBindBuffer(GL_ARRAY_BUFFER, g_stencilValues->BindVBO()); glBindVertexArray(0); }
//------------------------------------------------------------------------------ static void createVtrMesh(Shape * shape, int maxlevel) { Stopwatch s; s.Start(); // create Vtr mesh (topology) OpenSubdiv::Sdc::Type sdctype = GetSdcType(*shape); OpenSubdiv::Sdc::Options sdcoptions = GetSdcOptions(*shape); OpenSubdiv::Far::TopologyRefiner * refiner = OpenSubdiv::Far::TopologyRefinerFactory<Shape>::Create(sdctype, sdcoptions, *shape); OpenSubdiv::Far::PatchTables * patchTables = 0; if (g_Adaptive) { refiner->RefineAdaptive(maxlevel, /*fullTopology*/true); patchTables = OpenSubdiv::Far::PatchTablesFactory::Create(*refiner); g_numPatches = patchTables->GetNumPatches(); } else { refiner->RefineUniform(maxlevel, /*fullTopology*/true); } s.Stop(); // create vertex primvar data buffer std::vector<Vertex> vertexBuffer(refiner->GetNumVerticesTotal()); Vertex * verts = &vertexBuffer[0]; //printf("Vtr time: %f ms (topology)\n", float(s.GetElapsed())*1000.0f); // copy coarse vertices positions int ncoarseverts = shape->GetNumVertices(); for (int i=0; i<ncoarseverts; ++i) { float * ptr = &shape->verts[i*3]; verts[i].SetPosition(ptr[0], ptr[1], ptr[2]); } //#define no_stencils #ifdef no_stencils { s.Start(); // populate buffer with Vtr interpolated vertex data refiner->Interpolate(verts, verts + ncoarseverts); s.Stop(); //printf(" %f ms (interpolate)\n", float(s.GetElapsed())*1000.0f); //printf(" %f ms (total)\n", float(s.GetTotalElapsed())*1000.0f); } #else { OpenSubdiv::Far::StencilTablesFactory::Options options; options.generateOffsets=true; options.generateAllLevels=true; options.sortBySize=false; OpenSubdiv::Far::StencilTables const * stencilTables = OpenSubdiv::Far::StencilTablesFactory::Create(*refiner, options); stencilTables->UpdateValues(verts, verts + ncoarseverts); } #endif if (g_VtrDrawVertIDs) { createVertNumbers(*refiner, vertexBuffer); } if (g_VtrDrawFaceIDs) { createFaceNumbers(*refiner, vertexBuffer); } if (g_VtrDrawPtexIDs and patchTables) { createPtexNumbers(*patchTables, vertexBuffer); } if (g_Adaptive and patchTables) { createPatchNumbers(*patchTables, vertexBuffer); } createEdgeNumbers(*refiner, vertexBuffer, g_VtrDrawEdgeIDs!=0, g_VtrDrawEdgeSharpness!=0); GLMesh::Options options; options.vertColorMode=g_Adaptive ? GLMesh::VERTCOLOR_BY_LEVEL : GLMesh::VERTCOLOR_BY_SHARPNESS; options.edgeColorMode=g_Adaptive ? GLMesh::EDGECOLOR_BY_PATCHTYPE : GLMesh::EDGECOLOR_BY_SHARPNESS; options.faceColorMode=g_Adaptive ? GLMesh::FACECOLOR_BY_PATCHTYPE :GLMesh::FACECOLOR_SOLID; if (g_Adaptive) { g_vtr_glmesh.Initialize(options, *refiner, patchTables, (float *)&verts[0]); g_vtr_glmesh.SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f); } else { g_vtr_glmesh.Initialize(options, *refiner, patchTables, (float *)&verts[0]); g_vtr_glmesh.SetDiffuseColor(0.75f, 0.9f, 1.0f, 1.0f); } //setFaceColors(*refiner); g_vtr_glmesh.InitializeDeviceBuffers(); delete refiner; delete patchTables; }
MStatus MayaPolySmooth::compute( const MPlug& plug, MDataBlock& data ) { MStatus status; // Check which output attribute we have been asked to compute. If this // node doesn't know how to compute it, we must return // MS::kUnknownParameter. // if( plug == a_output ) { bool createdSubdMesh = false; int subdivisionLevel = data.inputValue(a_subdivisionLevels).asInt(); short stateH = data.inputValue(state).asShort(); if ((subdivisionLevel > 0) and (stateH !=1)) { // == Retrieve input mesh ==================================== // Get attr values MObject inMeshObj = data.inputValue(a_inputPolymesh).asMesh(); short vertBoundaryMethod = data.inputValue(a_vertBoundaryMethod).asShort(); short fvarBoundaryMethod = data.inputValue(a_fvarBoundaryMethod).asShort(); bool fvarPropCorners = data.inputValue(a_fvarPropagateCorners).asBool(); bool smoothTriangles = data.inputValue(a_smoothTriangles).asBool(); short creaseMethodVal = data.inputValue(a_creaseMethod).asShort(); // == Get Mesh Functions and Iterators ========================== MFnMeshData inMeshDat(inMeshObj); MFnMesh inMeshFn(inMeshObj, &status); MCHECKERR(status, "ERROR getting inMeshFn\n"); MItMeshPolygon inMeshItPolygon(inMeshObj, &status); MCHECKERR(status, "ERROR getting inMeshItPolygon\n"); // Convert attr values to OSD enums OpenSubdiv::Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK; // // Create Far topology // OpenSubdiv::Sdc::Options options; options.SetVtxBoundaryInterpolation(ConvertMayaVtxBoundary(vertBoundaryMethod)); options.SetFVarLinearInterpolation(ConvertMayaFVarBoundary(fvarBoundaryMethod, fvarPropCorners)); options.SetCreasingMethod(creaseMethodVal ? OpenSubdiv::Sdc::Options::CREASE_CHAIKIN : OpenSubdiv::Sdc::Options::CREASE_UNIFORM); options.SetTriangleSubdivision(smoothTriangles ? OpenSubdiv::Sdc::Options::TRI_SUB_SMOOTH : OpenSubdiv::Sdc::Options::TRI_SUB_CATMARK); float maxCreaseSharpness=0.0f; OpenSubdiv::Far::TopologyRefiner * refiner = gatherTopology(inMeshFn, inMeshItPolygon, type, options, &maxCreaseSharpness); assert(refiner); // Refine & Interpolate refiner->RefineUniform(OpenSubdiv::Far::TopologyRefiner::UniformOptions(subdivisionLevel)); Vertex const * controlVerts = reinterpret_cast<Vertex const *>(inMeshFn.getRawPoints(&status)); std::vector<Vertex> refinedVerts( refiner->GetNumVerticesTotal() - refiner->GetLevel(0).GetNumVertices()); Vertex const * srcVerts = controlVerts; Vertex * dstVerts = &refinedVerts[0]; for (int level = 1; level <= subdivisionLevel; ++level) { OpenSubdiv::Far::PrimvarRefiner(*refiner).Interpolate(level, srcVerts, dstVerts); srcVerts = dstVerts; dstVerts += refiner->GetLevel(level).GetNumVertices(); } // == Convert subdivided OpenSubdiv mesh to MFnMesh Data outputMesh ============= // Create New Mesh Data Object MFnMeshData newMeshData; MObject newMeshDataObj = newMeshData.create(&status); MCHECKERR(status, "ERROR creating outputData"); // Create out mesh status = convertToMayaMeshData(*refiner, refinedVerts, inMeshFn, newMeshDataObj); MCHECKERR(status, "ERROR convertOsdFarToMayaMesh"); // Propagate objectGroups from inMesh to outMesh (for per-facet shading, etc) status = createSmoothMesh_objectGroups(inMeshFn, inMeshDat, newMeshData, subdivisionLevel, refiner->GetLevel(subdivisionLevel).GetNumFaces()); // Write to output plug MDataHandle outMeshH = data.outputValue(a_output, &status); MCHECKERR(status, "ERROR getting polygon data handle\n"); outMeshH.set(newMeshDataObj); int isolation = std::min(10,(int)ceil(maxCreaseSharpness)+1); data.outputValue(a_recommendedIsolation).set(isolation); // == Cleanup OSD ============================================ // REVISIT: Re-add these deletes delete refiner; // note that the subd mesh was created (see the section below if !createdSubdMesh) createdSubdMesh = true; } // Pass-through inMesh to outMesh if not created the subd mesh if (!createdSubdMesh) { MDataHandle outMeshH = data.outputValue(a_output, &status); status = outMeshH.copy(data.outputValue(a_inputPolymesh, &status)); MCHECKERR(status, "ERROR getting polygon data handle\n"); } // Clean up Maya Plugs data.setClean(plug); } else { // Unhandled parameter in this compute function, so return MS::kUnknownParameter // so it is handled in a parent compute() function. return MS::kUnknownParameter; } return MS::kSuccess; }