From 4da7ec39940653894d09c6f37d713e4f3a5d2b04 Mon Sep 17 00:00:00 2001 From: Tyler Fox Date: Thu, 20 Jun 2019 15:32:16 -0700 Subject: [PATCH 01/12] Update a comment to be more informative --- blurRelax.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/blurRelax.cpp b/blurRelax.cpp index 075e1ff..e326bb0 100644 --- a/blurRelax.cpp +++ b/blurRelax.cpp @@ -632,10 +632,13 @@ void BlurRelax::buildQuickData( rawNeighbors.resize(numVertices); // Get the connectivity data - // The big trick is that all pinning happens once, right here - // If a vertex has no neighbors, then it is never smoothed - // so any vertices that are pinned just aren't given any neighbors - // (Meaning if A has neighbor B, then B doesn't necessarily have neighbor A) + + // Instead of treating neighbors as an un-directed graph, I treat it as a directed graph + // This way I can remove influences from vertices, but not their neighbors + // So I can remove all neighbors from a vertex to pin it, but its neighbors can still use it as an influence. + // The big trick is that all that pinning computation only happens once, right here. + // And after all this junk, I only have to apply one set of very simple, very optimizable rules + MItMeshEdge edgeIter(mesh); for (; !edgeIter.isDone(); edgeIter.next()) { const UINT start = edgeIter.index(0); From e7f1289c48ea3f85d2d26d903d242e2510ced2d8 Mon Sep 17 00:00:00 2001 From: Tyler Fox Date: Mon, 9 Sep 2019 18:28:58 -0700 Subject: [PATCH 02/12] Split out the DCC agnostic code so I can re-use it --- src/blurRelaxNode.cpp | 463 +++++++++++++++++++++++++++++++++++++++++ src/blurRelaxNode.h | 182 ++++++++++++++++ src/fastRelax.cpp | 364 ++++++++++++++++++++++++++++++++ src/fastRelax.h | 85 ++++++++ src/pluginRegister.cpp | 42 ++++ 5 files changed, 1136 insertions(+) create mode 100644 src/blurRelaxNode.cpp create mode 100644 src/blurRelaxNode.h create mode 100644 src/fastRelax.cpp create mode 100644 src/fastRelax.h create mode 100644 src/pluginRegister.cpp diff --git a/src/blurRelaxNode.cpp b/src/blurRelaxNode.cpp new file mode 100644 index 0000000..daa4d89 --- /dev/null +++ b/src/blurRelaxNode.cpp @@ -0,0 +1,463 @@ +/* +MIT License + +Copyright(c) 2018 Blur Studio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "external/xxhash.h" +#include "blurRelax.h" +#include "fastRelax.h" + + +MTypeId BlurRelax::id(0x001226FD); + +// local attributes +MObject BlurRelax::aIterations; +MObject BlurRelax::aBorderBehavior; +MObject BlurRelax::aHardEdgeBehavior; +MObject BlurRelax::aGroupEdgeBehavior; +MObject BlurRelax::aReproject; +MObject BlurRelax::aTaubinBias; + + +BlurRelax::BlurRelax() {} +BlurRelax::~BlurRelax() {} +void* BlurRelax::creator() { return new BlurRelax(); } + + +MStatus BlurRelax::getTrueWeights(MObject &mesh, MDataBlock &dataBlock, UINT index, std::vector &weightVals, float envelope) const { + // Maya might do some tricky stuff like not store the weight array at all for certain weight + // values so we can't count on an array existing in the weightList. For the OpenCL Kernel + // we want an array with one weight in it per vertex, we need to build it carefully here. + + // Two possibilities: we could have a sparse array in weightList[multiIndex] or there could be nothing in weightList[multiIndex]. + // if nothing is there then all the weights at 1.0f. + + MStatus status; + MFnMesh meshFn(mesh); + UINT numVerts = meshFn.numVertices(); + weightVals.reserve(numVerts); + + // Get a handle to the weight array we want. + MArrayDataHandle weightList = dataBlock.outputArrayValue(MPxDeformerNode::weightList, &status); + if (!status) return status; // we should always be able to get a weightList + status = weightList.jumpToElement(index); + // it is possible that the jumpToElement fails. In that case use the envelope value + if (!status) { + weightVals.resize(numVerts, envelope); + } + else { + MDataHandle hWeightsStructure = weightList.inputValue(&status); + if (!status) return status; + MArrayDataHandle hWeights = hWeightsStructure.child(MPxDeformerNode::weights); + if (!status) return status; + + // number of non-zero weights + unsigned int numWeights = hWeights.elementCount(&status); + if (!status) return status; + + // we're building a list with a weight per vertex, even if the weight is zero + unsigned int weightIndex = 0; + for (unsigned int i = 0; i < numWeights; i++, hWeights.next()) { + unsigned int weightsElementIndex = hWeights.elementIndex(&status); + while (weightIndex < weightsElementIndex) { + weightVals.push_back(envelope); // weights could be sparse, fill in default weight if no data + weightIndex++; + } + MDataHandle value = hWeights.inputValue(&status); + weightVals.push_back(value.asFloat() * envelope); + weightIndex++; + } + // now we have written the last non-zero weight into weightVals, but the last non-zero weight + // doesn't have to be for the last vertex in the buffer. Add more default values if necessary. + weightVals.resize(numVerts, envelope); + } + return MStatus::kSuccess; +} + +MStatus BlurRelax::initialize() { + MStatus status; + MFnEnumAttribute eAttr; + MFnNumericAttribute nAttr; + + aBorderBehavior = eAttr.create("borderBehavior", "bb", BB_SLIDE, &status); + CHECKSTAT("aBorderBehavior"); + eAttr.setKeyable(false); + eAttr.setChannelBox(true); + status = eAttr.addField("None", BB_NONE); + CHECKSTAT("aBorderBehavior"); + status = eAttr.addField("Pin", BB_PIN); + CHECKSTAT("aBorderBehavior"); + status = eAttr.addField("Slide", BB_SLIDE); + CHECKSTAT("aBorderBehavior"); + status = addAttribute(aBorderBehavior); + CHECKSTAT("aBorderBehavior"); + status = attributeAffects(aBorderBehavior, outputGeom); + CHECKSTAT("aBorderBehavior"); + + aHardEdgeBehavior = eAttr.create("hardEdgeBehavior", "hb", HB_SLIDE, &status); + CHECKSTAT("aHardEdgeBehavior"); + eAttr.setKeyable(false); + eAttr.setChannelBox(true); + status = eAttr.addField("None", HB_NONE); + CHECKSTAT("aHardEdgeBehavior"); + status = eAttr.addField("Pin", HB_PIN); + CHECKSTAT("aHardEdgeBehavior"); + status = eAttr.addField("Slide", HB_SLIDE); + CHECKSTAT("aHardEdgeBehavior"); + status = addAttribute(aHardEdgeBehavior); + CHECKSTAT("aHardEdgeBehavior"); + status = attributeAffects(aHardEdgeBehavior, outputGeom); + CHECKSTAT("aHardEdgeBehavior"); + + aGroupEdgeBehavior = eAttr.create("groupEdgeBehavior", "gb", GB_PIN, &status); + CHECKSTAT("aGroupEdgeBehavior"); + eAttr.setKeyable(false); + eAttr.setChannelBox(true); + status = eAttr.addField("None", GB_NONE); + CHECKSTAT("aGroupEdgeBehavior"); + status = eAttr.addField("Pin", GB_PIN); + CHECKSTAT("aGroupEdgeBehavior"); + status = eAttr.addField("Slide", GB_SLIDE); + CHECKSTAT("aGroupEdgeBehavior"); + status = addAttribute(aGroupEdgeBehavior); + CHECKSTAT("aGroupEdgeBehavior"); + status = attributeAffects(aGroupEdgeBehavior, outputGeom); + CHECKSTAT("aGroupEdgeBehavior"); + + aReproject = nAttr.create("reproject", "rp", MFnNumericData::kBoolean, false, &status); + CHECKSTAT("aReproject"); + nAttr.setKeyable(false); + nAttr.setChannelBox(true); + status = addAttribute(aReproject); + CHECKSTAT("aReproject"); + status = attributeAffects(aReproject, outputGeom); + CHECKSTAT("aReproject"); + + // Taubin Bias is divided by 1000 internally + aTaubinBias = nAttr.create("preserveVolume", "pv", MFnNumericData::kFloat, 0.0f, &status); + CHECKSTAT("aTaubinBias"); + nAttr.setMin(0.0f); + nAttr.setMax(2.0f); + nAttr.setChannelBox(true); + status = addAttribute(aTaubinBias); + CHECKSTAT("aTaubinBias"); + status = attributeAffects(aTaubinBias, outputGeom); + CHECKSTAT("aTaubinBias"); + + aIterations = nAttr.create("iterations", "i", MFnNumericData::kFloat, 0, &status); + CHECKSTAT("aIterations"); + nAttr.setMin(0.0); + nAttr.setChannelBox(true); + status = addAttribute(aIterations); + CHECKSTAT("aIterations"); + status = attributeAffects(aIterations, outputGeom); + CHECKSTAT("aIterations"); + + MGlobal::executeCommand("makePaintable -attrType \"multiFloat\" -sm \"deformer\" \"" DEFORMER_NAME "\" \"weights\";"); + + return MStatus::kSuccess; +} + +MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MMatrix& matrix, UINT multiIndex) { + // Short circuit if the envelope is zero + MStatus status; + MDataHandle hEnv = dataBlock.inputValue(envelope); + float env = hEnv.asFloat(); + + MDataHandle hIter = dataBlock.inputValue(aIterations); + float iterations = hIter.asFloat(); + + if (iterations > 0 && env > 0.0f){ + // Get the data from the node + MDataHandle hBorder = dataBlock.inputValue(aBorderBehavior); + short bb = hBorder.asShort(); + MDataHandle hHardEdge = dataBlock.inputValue(aHardEdgeBehavior); + short hb = hHardEdge.asShort(); + MDataHandle hGroupEdge = dataBlock.inputValue(aGroupEdgeBehavior); + short gb = hGroupEdge.asShort(); + MDataHandle hReproject = dataBlock.inputValue(aReproject); + bool reproject = hReproject.asBool(); + + MDataHandle hTBias = dataBlock.inputValue(aTaubinBias); + float tBias = hTBias.asFloat(); + // volume preservation uses 2 steps per iteration + // so half the number of iterations if I'm volume preserving + // The iterations interpolating as floats takes care of 99% of the jumping + // There *will* be a tiny jump from 0.0 on preserve volume if + // reprojection is also turned on. I don't think I can get rid of that one + if (tBias > 0.0f) { + iterations /= 2.0f; + } + // So the numbers shown are intuitive to the user + // 0 maps to 1.0 and 1 maps to -1.05 + // -1.05 is used because taubin smoothing needs something + // just a little over 1.0 to truly preserve volume, + // and that value looked good on my test mesh. + tBias = -2.05f * tBias + 1.0f; + + // get the input mesh corresponding to this output + MObject thisNode = this->thisMObject(); + MPlug inPlug(thisNode, input); + inPlug.selectAncestorLogicalIndex(multiIndex, input); + MDataHandle hInput = dataBlock.inputValue(inPlug); + MObject mesh = hInput.asMesh(); + + // Get the point values + MFnMesh meshFn(mesh); + UINT numVerts = meshFn.numVertices(); + + // Get the group data + std::vector group; + group.resize(numVerts); + for (; !vertIter.isDone(); vertIter.next()) { + group[vertIter.index()] = true; + } + vertIter.reset(); + + // Get the true smooth-edge data + std::vector trueSmoothEdges; + trueSmoothEdges.resize(meshFn.numEdges()); + MItMeshEdge edgeIter(mesh); + for (; !edgeIter.isDone(); edgeIter.next()) { + if (edgeIter.isSmooth()) { + trueSmoothEdges[edgeIter.index()] = true; + } + } + edgeIter.reset(); + + // Hash the topology to see if we need to re-build the data + // I could be smarter about this and only recompute things when needed + // however, those re-computations wouldn't save frames when it mattered + MIntArray mfaceCounts, mvertIdxs; + int *faceCounts, *vertIdxs; + meshFn.getVertices(mfaceCounts, mvertIdxs); + faceCounts = new int[mfaceCounts.length()]; + vertIdxs = new int[mvertIdxs.length()]; + /* + unsigned long long tFaceHash = XXH64(faceCounts, mfaceCounts.length()*sizeof(int), 0); + unsigned long long tEdgeHash = XXH64(vertIdxs, mvertIdxs.length()*sizeof(int), 0); + unsigned long long tGroupHash = XXH64(group.data(), group.size()*sizeof(char), 0); + unsigned long long tSmoothHash = XXH64(trueSmoothEdges.data(), trueSmoothEdges.size()*sizeof(char), 0); + */ + bool forceRecompute = false; + unsigned tFaceHash = mfaceCounts.length(); + unsigned tEdgeHash = mvertIdxs.length(); + unsigned tGroupHash = group.size(); + unsigned tSmoothHash = trueSmoothEdges.size(); + + delete[] faceCounts; + delete[] vertIdxs; + + if (forceRecompute || + (bbCheck != bb) || (hbCheck != hb) || (gbCheck != gb) || + (tFaceHash != faceHash) || (tEdgeHash != edgeHash) || + (tGroupHash != groupHash) || (tSmoothHash != smoothHash) + ) { + bbCheck = bb; hbCheck = hb; gbCheck = gb; + faceHash = tFaceHash; edgeHash = tEdgeHash; + groupHash = tGroupHash; smoothHash = tSmoothHash; + + // Populate the variables with *SPECIALLY ORDERED* data + // all vertex data is now shuffled by the order vector + + //std::vector> &neighbors, // A vector of neighbor indices per vertex + //std::vector> &hardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary + //std::vector &vertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, + loadMayaTopologyData(mesh, vertIter, rawNeighbors, rawHardEdges, rawVertData); + fillQuickTopoVars( + bb, hb, gb, rawNeighbors, rawHardEdges, rawVertData, + neighbors, creaseCount, shiftVal, valence, order, invOrder + ); + + } + + // This can happen if the user is pinning all the points + // or all the edges are hard (like when you import an obj) + if (neighbors.empty()) { + return status; + } + + // Build the raw float data buffers + pointArray_t mpa; + float_t(*reoVerts)[4] = new float_t[numVerts][4]; + meshFn.getPoints(mpa); + + for (size_t i = 0; i < numVerts; ++i) { + reoVerts[i][0] = mpa[order[i]].x; + reoVerts[i][1] = mpa[order[i]].y; + reoVerts[i][2] = mpa[order[i]].z; + reoVerts[i][3] = mpa[order[i]].w; + } + + // Calculate the relax, and store in verts + quickRelax(mesh, bb, hb, gb, reproject, tBias, iterations, numVerts, group, reoVerts); + + // Get the painted weight values + std::vector weightVals; + status = getTrueWeights(mesh, dataBlock, multiIndex, weightVals, env); + + // Finally set the output + for (; !vertIter.isDone(); vertIter.next()) { + const UINT idx = vertIter.index(); + vertIter.setPosition((weightVals[idx]) * point_t(reoVerts[invOrder[idx]]) + (1.0 - weightVals[idx]) * vertIter.position()); + } + + // Make sure to clean up after myself + delete [] reoVerts; + } + return status; +} + +void BlurRelax::quickRelax( + MObject &mesh, + const short borderBehavior, + const short hardEdgeBehavior, + const short groupEdgeBehavior, + const bool reproject, + const float taubinBias, + const float_t iterations, + const UINT numVerts, + const std::vector &group, + float_t(*verts)[4] +) { + bool rpEdges = (borderBehavior == BB_SLIDE) || (hardEdgeBehavior == HB_SLIDE) || (groupEdgeBehavior == GB_SLIDE); + std::vector groupIdxs; + + float_t (*baseVerts)[4]; + if (rpEdges) { + for (size_t i = 0; i < group.size(); ++i) { + if (group[i]) groupIdxs.push_back(i); + } + // make a copy of the original verts only if they'll be used for edge reprojection + baseVerts = new float_t[numVerts][4]; + memcpy(&(baseVerts[0][0]), &(verts[0][0]), 4 * numVerts * sizeof(float_t)); + } + + float_t(*prevVerts)[4]; + prevVerts = new float_t[numVerts][4]; + + float_t iterT, iterFI; + iterT = modf(iterations, &iterFI); + UINT iterI = (UINT)iterFI; + if (iterT > 0.0) { + iterI += 1; + } + + size_t nonzeroValence = neighbors[0].size(); + + MStatus status; + MMeshIntersector octree; + MObject smoothMeshPar, smoothMesh; + MFnMesh meshFn(mesh); + if (reproject) { + MFnMeshData smoothMeshParFn; + MMeshSmoothOptions smoothOpt; + smoothMeshPar = smoothMeshParFn.create(); + smoothOpt.setDivisions(1); + smoothOpt.setKeepBorderEdge(rpEdges); + smoothOpt.setSubdivisionType(MMeshSmoothOptions::kCatmullClark); + smoothMesh = meshFn.generateSmoothMesh(smoothMeshPar, &smoothOpt); + octree.create(smoothMesh); + } + + for (size_t r = 0; r < iterI; ++r) { + if ((r == iterI - 1) && (iterT > 0.0)){ + // Store the next-to-last iteration to interpolate with + memcpy(&(prevVerts[0][0]), &(verts[0][0]), 4 * numVerts * sizeof(float_t)); + } + quickLaplacianSmooth(verts, numVerts, neighbors, valence, shiftVal, pinPoints); + if (taubinBias < 1.0){ + quickLaplacianSmooth(verts, numVerts, neighbors, valence, shiftVal, pinPoints, taubinBias); + } + + if (rpEdges) { + edgeProject(baseVerts, groupIdxs, invOrder, neighbors, hardEdges, creaseCount, verts); + } + + if (reproject) { + #pragma omp parallel for if(numVerts>2000) + for (int i = 0; i < nonzeroValence; ++i) { + if ((creaseCount[i] == 0) && (group[order[i]])) { + point_t mf(verts[i][0], verts[i][1], verts[i][2]); + MPointOnMesh pom; + octree.getClosestPoint(mf, pom); + point_t gpf = pom.getPoint(); + //mfp.set(gpf, i); + verts[i][0] = gpf[0]; + verts[i][1] = gpf[1]; + verts[i][2] = gpf[2]; + } + } + } + } + + // Interpolate between prevVerts and verts based on iterT + if (iterT > 0.0) { + // This should vectorize + float_t * vv = &verts[0][0]; + float_t * pv = &prevVerts[0][0]; + for (size_t i = 0; i < numVerts * 4; ++i) { + vv[i] = ((vv[i] - pv[i]) * iterT) + pv[i]; + } + } + + if (rpEdges) delete[] baseVerts; + delete[] prevVerts; +} + diff --git a/src/blurRelaxNode.h b/src/blurRelaxNode.h new file mode 100644 index 0000000..969c3e5 --- /dev/null +++ b/src/blurRelaxNode.h @@ -0,0 +1,182 @@ +/* +MIT License + +Copyright(c) 2018 Blur Studio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "external/xxhash.h" + +#define CHECKSTAT(m) if (!status) {status.perror(m); return status;}; + +#define BB_NONE 0 +#define BB_PIN 1 +#define BB_SLIDE 2 + +#define HB_NONE 0 +#define HB_PIN 1 +#define HB_SLIDE 2 + +#define GB_NONE 0 +#define GB_PIN 1 +#define GB_SLIDE 2 + +#define SA_LAPLACIAN 0 +#define SA_TAUBIN 1 + +#define DEFORMER_NAME "BlurRelax" + +// Double vs Float +#define float_t double +#define point_t MPoint +#define pointArray_t MPointArray + + +void edgeProject( + const float_t basePoints[][4], + const std::vector &group, + const std::vector &invOrder, + const std::vector> &neighbors, + const std::vector> &hardEdges, + const std::vector &creaseCount, + float_t smoothPoints[][4] +); + +void quickLaplacianSmooth( + float_t verts2d[][4], + const size_t numVerts, + const std::vector> &neighbors, + const std::vector &valence, + const std::vector &shiftVal, + const std::vector &pinPoints, + const float_t taubinBias=1.0 +); + +class BlurRelax : public MPxDeformerNode { + public: + BlurRelax(); + ~BlurRelax() override; + + static void* creator(); + static MStatus initialize(); + + MStatus deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MMatrix& matrix, UINT multiIndex) override; + + public: + // local node attributes + static MObject aIterations; + static MObject aBorderBehavior; + static MObject aHardEdgeBehavior; + static MObject aGroupEdgeBehavior; + static MObject aReproject; + static MObject aTaubinBias; + static MTypeId id; + private: + // Hash checking variables + unsigned long long faceHash = 0; + unsigned long long edgeHash = 0; + unsigned long long groupHash = 0; + unsigned long long smoothHash = 0; + short bbCheck = 255; + short hbCheck = 255; + short gbCheck = 255; + + // storage for this data doesn't change unless the hashes do + std::vector order; + std::vector invOrder; + std::vector> neighbors; + std::vector> hardEdges; + std::vector shiftVal; // normally 0.5; but it's 0.25 if on a hard edge + std::vector valence; // as float for vectorizing + std::vector pinPoints; + std::vector creaseCount; + + + MStatus getTrueWeights( + MObject &mesh, + MDataBlock &dataBlock, + UINT index, + std::vector &weightVals, + float envelope + ) const; + + void BlurRelax::buildQuickData( + MObject &mesh, + MItGeometry& vertIter, + short borderBehavior, + short hardEdgeBehavior, + short groupEdgeBehavior, + bool reproject, + std::vector &group + ); + + void quickRelax( + MObject &mesh, + const short borderBehavior, + const short hardEdgeBehavior, + const short groupEdgeBehavior, + const bool reproject, + const float taubinBias, + const float_t iterations, + const UINT numVerts, + const std::vector &group, + float_t(*verts)[4] // already resized + ); +}; + + + diff --git a/src/fastRelax.cpp b/src/fastRelax.cpp new file mode 100644 index 0000000..8c04141 --- /dev/null +++ b/src/fastRelax.cpp @@ -0,0 +1,364 @@ + +/* +MIT License + +Copyright(c) 2018 Blur Studio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#include +#include +#include +#include +#include "fastRelax.h" + +/* + This code takes the edges that have been defined as hard + and reprojects them onto the original neighboring hard edges + + Perhaps in the future, I can project onto non-branching + strings of edges. But for now, it's just direct neighbors +*/ +void edgeProject( + const float_t basePoints[][4], + const std::vector &group, + const std::vector &invOrder, + const std::vector> &neighbors, + const std::vector &creaseCount, + float_t smoothPoints[][4] +) { + std::vector neigh; + for (size_t gidx = 0; gidx < group.size(); ++gidx) { + // If we have "hard edges" we have already removed + // any soft edges, so if the crease count for this vertex is zero + // we can just completely skip it. And if it's >0, then we can + // assume that all stored neighbors are hard. + size_t idx = invOrder[group[gidx]]; + + float_t *avg = smoothPoints[idx]; + const float_t *basePos = basePoints[idx]; + if (creaseCount[idx] != 2) { continue; } + float_t keep[3], delta[3], edge[3]; + + neigh.clear(); + for (size_t i = 0; i < neighbors.size(); ++i) { + if (idx < neighbors[i].size()) { + neigh.push_back(neighbors[i][idx]); + } + else break; + } + + // TODO: Find the hard edge strings and reproject onto those + float_t minLen = std::numeric_limits::max(); + bool found = false; + + // avg - basePos + for (size_t x = 0; x < 3; ++x) + delta[x] = avg[x] - basePos[x]; + + for (size_t i = 0; i < neigh.size(); ++i) { + size_t n = neigh[i]; + + // normalized(prevPoints[n] - basePos) + for (size_t x = 0; x < 3; ++x) + edge[x] = basePoints[n][x] - basePos[x]; + float_t elen = sqrt(edge[0] * edge[0] + edge[1] * edge[1] + edge[2] * edge[2]); + for (size_t x = 0; x < 3; ++x) + edge[x] /= elen; + + // dot(delta, edge) + float_t dd = delta[0] * edge[0] + delta[1] * edge[1] + delta[2] * edge[2]; + + float_t dn[3] = { 0.0f, 0.0f, 0.0f }; + if (dd > 0.0) { + for (size_t x = 0; x < 3; ++x) + dn[x] = edge[x] * dd; + } + float_t xx[3]; + + // delta - dn + for (size_t x = 0; x < 3; ++x) + xx[x] = delta[x] - dn[x]; + + // dot(xx, xx) + float_t len2 = xx[0] * xx[0] + xx[1] * xx[1] + xx[2] * xx[2]; + + if (len2 < minLen) { + minLen = len2; + for (size_t x = 0; x < 3; ++x) + keep[x] = dn[x]; + found = true; + } + } + + if (found) { + for (size_t x = 0; x < 3; ++x) + avg[x] = basePos[x] + keep[x]; + } + + } +} + + +/* + All the crazy hoops I've jumped through are to make auto-vectorization work + in this function. + + neighbors and neighborOffsets work together to be a kind of pseudo-transpose + of a std::vector of neighbors per vert with that vector sorted so the verts + with the most neighbors were at the top. + neighborOffsets contains the index offsets of vertices with at least [index] of neighbors +*/ +void quickLaplacianSmooth( + float_t verts2d[][4], + const size_t numVerts, + const std::vector> &neighbors, + const std::vector &valence, + const std::vector &shiftVal, + const float_t taubinBias=1.0 +) { + // First, get verts as a single pointer to the contiguous memory stored in (verts2d*)[4] + float_t* verts = &(verts2d[0][0]); + + // number of nonzero valence + size_t nzv = neighbors[0].size(); + + // number of nonzero components + size_t nzc = 4 * nzv; + + // The __restrict keyword tells the compiler that *outComp + // is not pointed to by any other pointer in this scope + // This allows for auto-vectorization + float_t * __restrict outComp = new float_t[nzc]; + memset(outComp, 0, nzc*sizeof(float_t)); + + for (size_t ncIdx = 0; ncIdx < neighbors.size(); ++ncIdx) { + const auto &nCol = neighbors[ncIdx]; + size_t nColCount = nCol.size(); + for (size_t i = 0; i < nColCount; ++i) { + size_t nci = 4 * nCol[i]; + for (size_t j = 0; j < 4; ++j) { + outComp[4*i+j] = outComp[4*i+j] + verts[nci+j]; + } + } + } + + for (size_t i = 0; i < nzc; ++i) { + outComp[i] = shiftVal[i] * taubinBias * ((outComp[i] / valence[i]) - verts[i]) + verts[i]; + } + + memcpy(verts, outComp, nzc*sizeof(float_t)); + delete outComp; +} + + +/* + I'm trying to pre-process everything I can right here so I don't have to + have any 'if's in quickLaplacianSmooth() so auto-vectorization works +*/ +void fillQuickTopoVars( + // Behaviors + short borderBehavior, // BB_NONE/BB_PIN/BB_SLIDE + short hardEdgeBehavior, // HB_NONE/HB_PIN/HB_SLIDE + short groupEdgeBehavior, // GB_NONE/GB_PIN/GB_SLIDE + + // Inputs + std::vector> rawNeighbors, // A vector of neighbor indices per vertex. Copied + std::vector> rawHardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary. Copied + const std::vector &rawVertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, + + // Outputs + std::vector> &neighbors, + std::vector &creaseCount, + std::vector &shiftVal, + std::vector &valence, + std::vector &order, + std::vector &invOrder +){ + size_t numVertices = rawVertData.size(); + + // Read the input data and define the per-point behavior + std::vector rawPinPoints; + std::vector rawCreaseCount; + rawPinPoints.resize(numVertices); + rawCreaseCount.resize(numVertices); + for (size_t start=0; start rawShiftVal; + rawShiftVal.resize(numVertices); + std::fill(rawShiftVal.begin(), rawShiftVal.end(), 0.5); + + // Apply the "behavior transformations" to the input data + // if a vert has hard neighbors, remove all non-hard neighbors + // if a vert is pinned remove all of its neighbors + for (size_t i = 0; i < rawNeighbors.size(); ++i) { + bool pinThisPoint = rawPinPoints[i]; + if ((rawCreaseCount[i] != 0) || pinThisPoint) { + rawShiftVal[i] = 0.25; + if (rawCreaseCount[i] != 2) pinThisPoint = true; + if (!pinThisPoint) { + std::vector newNeigh; + std::vector newHard; + for (size_t j = 0; j < rawNeighbors[i].size(); ++j) { + if (rawHardEdges[i][j] & E_HARD) { + newNeigh.push_back(rawNeighbors[i][j]); + newHard.push_back(rawHardEdges[i][j]); + } + } + rawNeighbors[i] = newNeigh; + rawHardEdges[i] = newHard; + } + } + } + + // Reading the data is done. Now we transform the data for fast processing + size_t maxValence = rawNeighbors[order[0]].size(); + neighbors.resize(maxValence); + order.resize(numVertices); + invOrder.resize(numVertices); + creaseCount.resize(numVertices); + shiftVal.resize(numVertices * NUM_COMPS); + valence.resize(numVertices * NUM_COMPS); + + // Sort the verts by their valence count. Compare using > for a sort from high to low + // Then build the inverse sort indexes + std::iota(order.begin(), order.end(), 0u); + std::sort(order.begin(), order.end(), [&rawNeighbors](size_t a, size_t b) {return rawNeighbors[a].size() > rawNeighbors[b].size(); }); + for (size_t i = 0; i < order.size(); ++i) { invOrder[order[i]] = i; } + + // Build the "transposed" neighbor and hard edge values + for (size_t i = 0; i < numVertices; ++i) { + const auto &neigh = rawNeighbors[order[i]]; + const auto &hards = rawHardEdges[order[i]]; + size_t vale = neigh.size(); + for (size_t n = 0; n < vale; ++n) { + neighbors[n].push_back(invOrder[neigh[n]]); + } + // only needed for slide and reproject + // and I only need to know if it's ==0 or ==2 + creaseCount[i] = rawCreaseCount[order[i]]; + + // Vectorizing flattens the vert list, so I need this data per vert, per component + // Maya uses xyzw points, so I need 4. In other cases I'll need 3 + for (size_t xx = 0; xx < NUM_COMPS; ++xx) { + valence[NUM_COMPS * i + xx] = float_t(vale); + shiftVal[NUM_COMPS * i + xx] = rawShiftVal[order[i]]; + } + } +} + + +/* + Load the minimal topology data from Maya +*/ +void loadMayaTopologyData( + // Inputs + MObject &mesh, + MItGeometry& vertIter, + + //outputs + std::vector> &neighbors, // A vector of neighbor indices per vertex + std::vector> &hardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary + std::vector &vertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, +){ + MFnMesh meshFn(mesh); + UINT numVertices = meshFn.numVertices(); + vertData.resize(numVertices); + hardEdges.resize(numVertices); + neighbors.resize(numVertices); + + for (; !vertIter.isDone(); vertIter.next()) { + vertData[vertIter.index()] = V_IN_GROUP; + } + vertIter.reset(); + + MItMeshEdge edgeIter(mesh); + for (; !edgeIter.isDone(); edgeIter.next()) { + const UINT start = edgeIter.index(0); + const UINT end = edgeIter.index(1); + + char edgeData; + if (edgeIter.onBoundary()) { + edgeData |= E_MESH_BORDER; + vertData[start] |= V_MESH_BORDER; + vertData[end] |= V_MESH_BORDER; + } + if (!edgeIter.isSmooth()) edgeData |= E_HARD; + + rawNeighbors[start].push_back(end); + rawNeighbors[end].push_back(start); + + hardEdges[start].push_back(edgeData); + hardEdges[end].push_back(edgeData); + + if (vertData[start] & V_IN_GROUP) { + if (!(vertData[end] & V_IN_GROUP)) vertData[start] |= V_GROUP_BORDER; + } + else if (vertData[end] & V_IN_GROUP) vertData[end] |= V_GROUP_BORDER; + + // an edge is a group border edge iff + // one of the two faces bordering this edge + // has a vertex that is not in the group + bool internalEdge = true; + MIntArray connFaces; + edgeIter.getConnectedFaces(connFaces); + for (UINT i=0; i +#include +#include + +#define V_IN_GROUP 1 +#define V_MESH_BORDER 2 +#define V_GROUP_BORDER 4 + +#define E_HARD 1 +#define E_MESH_BORDER 2 +#define E_GROUP_BORDER 4 + +#define NUM_COMPS 4 + + +void edgeProject( + const float_t basePoints[][NUM_COMPS], + const std::vector &group, + const std::vector &invOrder, + const std::vector> &neighbors, + const std::vector &creaseCount, + float_t smoothPoints[][NUM_COMPS] +); + +void quickLaplacianSmooth( + float_t verts2d[][NUM_COMPS], + const size_t numVerts, + const std::vector> &neighbors, + const std::vector &valence, + const std::vector &shiftVal, + const float_t taubinBias=1.0 +); + +void loadMayaTopologyData( + MObject &mesh, + MItGeometry& vertIter, + std::vector> &neighbors, // A vector of neighbor indices per vertex + std::vector> &hardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary + std::vector &vertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, +); + +void fillQuickTopoVars( + // Behaviors + short borderBehavior, // BB_NONE/BB_PIN/BB_SLIDE + short hardEdgeBehavior, // HB_NONE/HB_PIN/HB_SLIDE + short groupEdgeBehavior, // GB_NONE/GB_PIN/GB_SLIDE + + // Inputs + std::vector> rawNeighbors, // A vector of neighbor indices per vertex. Copied + std::vector> rawHardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary. Copied + const std::vector &rawVertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, + + // Outputs + std::vector> &neighbors, + std::vector &creaseCount, + std::vector &shiftVal, + std::vector &valence, + std::vector &order, + std::vector &invOrder +); + diff --git a/src/pluginRegister.cpp b/src/pluginRegister.cpp new file mode 100644 index 0000000..27b84e6 --- /dev/null +++ b/src/pluginRegister.cpp @@ -0,0 +1,42 @@ +/* +MIT License + +Copyright(c) 2018 Blur Studio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include "blurRelax.h" + +MStatus initializePlugin(MObject obj) { + MStatus result; + MFnPlugin plugin(obj, "Blur Studio", "1.0", "Any"); + result = plugin.registerNode(DEFORMER_NAME, BlurRelax::id, BlurRelax::creator, BlurRelax::initialize, MPxNode::kDeformerNode); + + return result; +} + +MStatus uninitializePlugin(MObject obj) { + MStatus result; + MFnPlugin plugin(obj); + result = plugin.deregisterNode(BlurRelax::id); + return result; +} + From dea73be40b656d6b192f9b4e8284e6b8394ba26d Mon Sep 17 00:00:00 2001 From: Tyler Fox Date: Tue, 10 Sep 2019 18:44:29 -0700 Subject: [PATCH 03/12] Add the delta mush inputs and remove the complex hash --- src/blurRelaxNode.cpp | 107 ++++++++++++++++++++---------------------- src/blurRelaxNode.h | 20 ++++---- 2 files changed, 63 insertions(+), 64 deletions(-) diff --git a/src/blurRelaxNode.cpp b/src/blurRelaxNode.cpp index daa4d89..57341df 100644 --- a/src/blurRelaxNode.cpp +++ b/src/blurRelaxNode.cpp @@ -73,6 +73,10 @@ MObject BlurRelax::aHardEdgeBehavior; MObject BlurRelax::aGroupEdgeBehavior; MObject BlurRelax::aReproject; MObject BlurRelax::aTaubinBias; +MObject BlurRelax::aRecomputeTopo; +MObject BlurRelax::aDeltaMush; +MObject BlurRelax::aDeltaMult; +MObject BlurRelax::aDeltaBase; BlurRelax::BlurRelax() {} @@ -134,6 +138,7 @@ MStatus BlurRelax::initialize() { MStatus status; MFnEnumAttribute eAttr; MFnNumericAttribute nAttr; + MFnTypedAttribute tAttr; aBorderBehavior = eAttr.create("borderBehavior", "bb", BB_SLIDE, &status); CHECKSTAT("aBorderBehavior"); @@ -209,6 +214,37 @@ MStatus BlurRelax::initialize() { status = attributeAffects(aIterations, outputGeom); CHECKSTAT("aIterations"); + aRecomputeTopo = nAttr.create("recompute", "r", MFnNumericData::kBoolean, false, &status); + CHECKSTAT("aRecomputeTopo"); + status = addAttribute(aRecomputeTopo); + CHECKSTAT("aRecomputeTopo"); + status = attributeAffects(aRecomputeTopo, outputGeom); + CHECKSTAT("aRecomputeTopo"); + + aDeltaMush = nAttr.create("delta", "d", MFnNumericData::kBoolean, false, &status); + CHECKSTAT("aDeltaMush"); + status = addAttribute(aDeltaMush); + CHECKSTAT("aDeltaMush"); + status = attributeAffects(aDeltaMush, outputGeom); + CHECKSTAT("aDeltaMush"); + + aDeltaMult = nAttr.create("deltaMultiplier", "dm", MFnNumericData::kFloat, 1.0f, &status); + CHECKSTAT("aDeltaMult"); + nAttr.setChannelBox(true); + status = addAttribute(aDeltaMult); + CHECKSTAT("aDeltaMult"); + status = attributeAffects(aDeltaMult, outputGeom); + CHECKSTAT("aDeltaMult"); + + aDeltaBase = tAttr.create("deltaBase", "db", MFnData::kMesh); + tAttr.setArray(true); + CHECKSTAT("aDeltaBase"); + status = addAttribute(aDeltaBase); + CHECKSTAT("aDeltaBase"); + status = attributeAffects(aDeltaBase, outputGeom); + CHECKSTAT("aDeltaBase"); + + MGlobal::executeCommand("makePaintable -attrType \"multiFloat\" -sm \"deformer\" \"" DEFORMER_NAME "\" \"weights\";"); return MStatus::kSuccess; @@ -233,6 +269,8 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM short gb = hGroupEdge.asShort(); MDataHandle hReproject = dataBlock.inputValue(aReproject); bool reproject = hReproject.asBool(); + MDataHandle hRecompute = dataBlock.inputValue(aRecomputeTopo); + bool recompute = hRecompute.asBool(); MDataHandle hTBias = dataBlock.inputValue(aTaubinBias); float tBias = hTBias.asFloat(); @@ -260,71 +298,28 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM // Get the point values MFnMesh meshFn(mesh); - UINT numVerts = meshFn.numVertices(); + int tNumVerts = meshFn.numVertices(); + int tNumPolys = meshFn.numPolygons(); + int tNumEdges = meshFn.numEdges(); - // Get the group data - std::vector group; - group.resize(numVerts); - for (; !vertIter.isDone(); vertIter.next()) { - group[vertIter.index()] = true; - } - vertIter.reset(); - - // Get the true smooth-edge data - std::vector trueSmoothEdges; - trueSmoothEdges.resize(meshFn.numEdges()); - MItMeshEdge edgeIter(mesh); - for (; !edgeIter.isDone(); edgeIter.next()) { - if (edgeIter.isSmooth()) { - trueSmoothEdges[edgeIter.index()] = true; - } - } - edgeIter.reset(); - - // Hash the topology to see if we need to re-build the data - // I could be smarter about this and only recompute things when needed - // however, those re-computations wouldn't save frames when it mattered - MIntArray mfaceCounts, mvertIdxs; - int *faceCounts, *vertIdxs; - meshFn.getVertices(mfaceCounts, mvertIdxs); - faceCounts = new int[mfaceCounts.length()]; - vertIdxs = new int[mvertIdxs.length()]; - /* - unsigned long long tFaceHash = XXH64(faceCounts, mfaceCounts.length()*sizeof(int), 0); - unsigned long long tEdgeHash = XXH64(vertIdxs, mvertIdxs.length()*sizeof(int), 0); - unsigned long long tGroupHash = XXH64(group.data(), group.size()*sizeof(char), 0); - unsigned long long tSmoothHash = XXH64(trueSmoothEdges.data(), trueSmoothEdges.size()*sizeof(char), 0); - */ - bool forceRecompute = false; - unsigned tFaceHash = mfaceCounts.length(); - unsigned tEdgeHash = mvertIdxs.length(); - unsigned tGroupHash = group.size(); - unsigned tSmoothHash = trueSmoothEdges.size(); - - delete[] faceCounts; - delete[] vertIdxs; - - if (forceRecompute || + if (recompute || (bbCheck != bb) || (hbCheck != hb) || (gbCheck != gb) || - (tFaceHash != faceHash) || (tEdgeHash != edgeHash) || - (tGroupHash != groupHash) || (tSmoothHash != smoothHash) + (tNumVerts != hNumVerts) || (tNumPolys != hNumPolys) || (tNumEdges != hNumEdges) ) { bbCheck = bb; hbCheck = hb; gbCheck = gb; - faceHash = tFaceHash; edgeHash = tEdgeHash; - groupHash = tGroupHash; smoothHash = tSmoothHash; - + hNumVerts = tNumVerts; hNumPolys = tNumPolys; hNumEdges = tNumEdges; // Populate the variables with *SPECIALLY ORDERED* data // all vertex data is now shuffled by the order vector - //std::vector> &neighbors, // A vector of neighbor indices per vertex - //std::vector> &hardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary - //std::vector &vertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, + std::vector> rawNeighbors; // A vector of neighbor indices per vertex + std::vector> rawHardEdges; // Bitwise per-neighbor data: edge is hard, edge along boundary + std::vector rawVertData; // Bitwise per-vert data: Group membership, geo boundary, group boundary, + loadMayaTopologyData(mesh, vertIter, rawNeighbors, rawHardEdges, rawVertData); fillQuickTopoVars( bb, hb, gb, rawNeighbors, rawHardEdges, rawVertData, neighbors, creaseCount, shiftVal, valence, order, invOrder ); - } // This can happen if the user is pinning all the points @@ -332,7 +327,7 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM if (neighbors.empty()) { return status; } - + // Build the raw float data buffers pointArray_t mpa; float_t(*reoVerts)[4] = new float_t[numVerts][4]; @@ -421,9 +416,9 @@ void BlurRelax::quickRelax( // Store the next-to-last iteration to interpolate with memcpy(&(prevVerts[0][0]), &(verts[0][0]), 4 * numVerts * sizeof(float_t)); } - quickLaplacianSmooth(verts, numVerts, neighbors, valence, shiftVal, pinPoints); + quickLaplacianSmooth(verts, numVerts, neighbors, valence, shiftVal); if (taubinBias < 1.0){ - quickLaplacianSmooth(verts, numVerts, neighbors, valence, shiftVal, pinPoints, taubinBias); + quickLaplacianSmooth(verts, numVerts, neighbors, valence, shiftVal, taubinBias); } if (rpEdges) { diff --git a/src/blurRelaxNode.h b/src/blurRelaxNode.h index 969c3e5..eab2f5e 100644 --- a/src/blurRelaxNode.h +++ b/src/blurRelaxNode.h @@ -124,13 +124,16 @@ class BlurRelax : public MPxDeformerNode { static MObject aGroupEdgeBehavior; static MObject aReproject; static MObject aTaubinBias; + static MObject aRecomputeTopo; + static MObject aDeltaMush; + static MObject aDeltaMult; + static MObject aDeltaBase; static MTypeId id; private: // Hash checking variables - unsigned long long faceHash = 0; - unsigned long long edgeHash = 0; - unsigned long long groupHash = 0; - unsigned long long smoothHash = 0; + int hNumVerts = 0; + int hNumPolys = 0; + int hNumEdges = 0; short bbCheck = 255; short hbCheck = 255; short gbCheck = 255; @@ -138,14 +141,15 @@ class BlurRelax : public MPxDeformerNode { // storage for this data doesn't change unless the hashes do std::vector order; std::vector invOrder; - std::vector> neighbors; - std::vector> hardEdges; + + + std::vector> &neighbors, // A vector of neighbor indices per vertex + std::vector> &hardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary + std::vector &vertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, std::vector shiftVal; // normally 0.5; but it's 0.25 if on a hard edge std::vector valence; // as float for vectorizing - std::vector pinPoints; std::vector creaseCount; - MStatus getTrueWeights( MObject &mesh, MDataBlock &dataBlock, From 6415004badc9cb12e4e0e9dc4b3b86e572cff76d Mon Sep 17 00:00:00 2001 From: Tyler Fox Date: Wed, 11 Sep 2019 10:07:23 -0700 Subject: [PATCH 04/12] Remove the old code, and update the CMakeLists.txt --- CMakeLists.txt | 8 +- blurRelax.cpp | 878 ----------------------------------- external/xxhash.c | 1029 ----------------------------------------- external/xxhash.h | 328 ------------- src/blurRelaxNode.cpp | 1 - src/blurRelaxNode.h | 1 - 6 files changed, 5 insertions(+), 2240 deletions(-) delete mode 100644 blurRelax.cpp delete mode 100644 external/xxhash.c delete mode 100644 external/xxhash.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 79a3e54..68041cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,9 +8,11 @@ find_package(OpenGL REQUIRED) set(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/../output/Maya${MAYA_VERSION}) set(SOURCE_FILES - "blurRelax.cpp" - "external/xxhash.h" - "external/xxhash.c" + "src/blurRelaxNode.cpp" + "src/blurRelaxNode.h" + "src/fastRelax.cpp" + "src/fastRelax.h" + "src/pluginRegister.cpp" ) include_directories(${MAYA_INCLUDE_DIR}) diff --git a/blurRelax.cpp b/blurRelax.cpp deleted file mode 100644 index e326bb0..0000000 --- a/blurRelax.cpp +++ /dev/null @@ -1,878 +0,0 @@ -/* -MIT License - -Copyright(c) 2018 Blur Studio - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files(the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions : - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "external/xxhash.h" - -#define CHECKSTAT(m) if (!status) {status.perror(m); return status;}; - -#define BB_NONE 0 -#define BB_PIN 1 -#define BB_SLIDE 2 - -#define HB_NONE 0 -#define HB_PIN 1 -#define HB_SLIDE 2 - -#define GB_NONE 0 -#define GB_PIN 1 -#define GB_SLIDE 2 - -#define SA_LAPLACIAN 0 -#define SA_TAUBIN 1 - -#define DEFORMER_NAME "BlurRelax" - -// Double vs Float -#define float_t double -#define point_t MPoint -#define pointArray_t MPointArray - - -void edgeProject( - const float_t basePoints[][4], - const std::vector &group, - const std::vector &invOrder, - const std::vector> &neighbors, - const std::vector> &hardEdges, - const std::vector &creaseCount, - float_t smoothPoints[][4] -) { - /* - This code takes the edges that have been defined as hard - and reprojects them onto the original neighboring hard edges - - Perhaps in the future, I can project onto non-branching - strings of edges. But for now, it's just direct neighbors - */ - - std::vector neigh; - for (size_t gidx = 0; gidx < group.size(); ++gidx) { - // If we have "hard edges" we have already removed - // any soft edges, so if the crease count for this vertex is zero - // we can just completely skip it. And if it's >0, then we can - // assume that all stored neighbors are hard. - size_t idx = invOrder[group[gidx]]; - - float_t *avg = smoothPoints[idx]; - const float_t *basePos = basePoints[idx]; - if (creaseCount[idx] != 2) { continue; } - float_t keep[3], delta[3], edge[3]; - - neigh.clear(); - for (size_t i = 0; i < neighbors.size(); ++i) { - if (idx < neighbors[i].size()) { - neigh.push_back(neighbors[i][idx]); - } - else break; - } - - // TODO: Find the hard edge strings and reproject onto those - float_t minLen = std::numeric_limits::max(); - bool found = false; - - // avg - basePos - for (size_t x = 0; x < 3; ++x) - delta[x] = avg[x] - basePos[x]; - - for (size_t i = 0; i < neigh.size(); ++i) { - size_t n = neigh[i]; - - // normalized(prevPoints[n] - basePos) - for (size_t x = 0; x < 3; ++x) - edge[x] = basePoints[n][x] - basePos[x]; - float_t elen = sqrt(edge[0] * edge[0] + edge[1] * edge[1] + edge[2] * edge[2]); - for (size_t x = 0; x < 3; ++x) - edge[x] /= elen; - - // dot(delta, edge) - float_t dd = delta[0] * edge[0] + delta[1] * edge[1] + delta[2] * edge[2]; - - float_t dn[3] = { 0.0f, 0.0f, 0.0f }; - if (dd > 0.0) { - for (size_t x = 0; x < 3; ++x) - dn[x] = edge[x] * dd; - } - float_t xx[3]; - - // delta - dn - for (size_t x = 0; x < 3; ++x) - xx[x] = delta[x] - dn[x]; - - // dot(xx, xx) - float_t len2 = xx[0] * xx[0] + xx[1] * xx[1] + xx[2] * xx[2]; - - if (len2 < minLen) { - minLen = len2; - for (size_t x = 0; x < 3; ++x) - keep[x] = dn[x]; - found = true; - } - } - - if (found) { - for (size_t x = 0; x < 3; ++x) - avg[x] = basePos[x] + keep[x]; - } - - } -} - -void quickLaplacianSmooth( - float_t verts2d[][4], - const size_t numVerts, - const std::vector> &neighbors, - const std::vector &valence, - const std::vector &shiftVal, - const std::vector &pinPoints, - const float_t taubinBias=1.0 -) { - /* - All the crazy hoops I've jumped through are to make auto-vectorization work - - neighbors and neighborOffsets work together to be a kind of pseudo-transpose - of a std::vector of neighbors per vert with that vector sorted so the verts - with the most neighbors were at the top. - neighborOffsets contains the index offsets of vertices with at least [index] of neighbors - */ - - // First, get verts as a single pointer to the contiguous memory stored in (verts2d*)[4] - float_t* verts = &(verts2d[0][0]); - - // number of nonzero valence - size_t nzv = neighbors[0].size(); - - // number of nonzero components - size_t nzc = 4 * nzv; - - // The __restrict keyword tells the compiler that *outComp - // is not pointed to by any other pointer in this scope - // This allows for auto-vectorization - float_t * __restrict outComp = new float_t[nzc]; - memset(outComp, 0, nzc*sizeof(float_t)); - - for (size_t ncIdx = 0; ncIdx < neighbors.size(); ++ncIdx) { - const auto &nCol = neighbors[ncIdx]; - size_t nColCount = nCol.size(); - for (size_t i = 0; i < nColCount; ++i) { - size_t nci = 4 * nCol[i]; - for (size_t j = 0; j < 4; ++j) { - outComp[4*i+j] = outComp[4*i+j] + verts[nci+j]; - } - } - } - - for (size_t i = 0; i < nzc; ++i) { - outComp[i] = shiftVal[i] * taubinBias * ((outComp[i] / valence[i]) - verts[i]) + verts[i]; - } - - memcpy(verts, outComp, nzc*sizeof(float_t)); - delete outComp; -} - - -class BlurRelax : public MPxDeformerNode { - public: - BlurRelax(); - ~BlurRelax() override; - - static void* creator(); - static MStatus initialize(); - - MStatus deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MMatrix& matrix, UINT multiIndex) override; - - public: - // local node attributes - static MObject aIterations; - static MObject aBorderBehavior; - static MObject aHardEdgeBehavior; - static MObject aGroupEdgeBehavior; - static MObject aReproject; - static MObject aTaubinBias; - static MTypeId id; - private: - // Hash checking variables - unsigned long long faceHash = 0; - unsigned long long edgeHash = 0; - unsigned long long groupHash = 0; - unsigned long long smoothHash = 0; - short bbCheck = 255; - short hbCheck = 255; - short gbCheck = 255; - - // storage for this data doesn't change unless the hashes do - std::vector order; - std::vector invOrder; - std::vector> neighbors; - std::vector> hardEdges; - std::vector shiftVal; // normally 0.5; but it's 0.25 if on a hard edge - std::vector valence; // as float for vectorizing - std::vector pinPoints; - std::vector creaseCount; - - - MStatus getTrueWeights( - MObject &mesh, - MDataBlock &dataBlock, - UINT index, - std::vector &weightVals, - float envelope - ) const; - - void BlurRelax::buildQuickData( - MObject &mesh, - MItGeometry& vertIter, - short borderBehavior, - short hardEdgeBehavior, - short groupEdgeBehavior, - bool reproject, - std::vector &group - ); - - void quickRelax( - MObject &mesh, - const short borderBehavior, - const short hardEdgeBehavior, - const short groupEdgeBehavior, - const bool reproject, - const float taubinBias, - const float_t iterations, - const UINT numVerts, - const std::vector &group, - float_t(*verts)[4] // already resized - ); -}; - -MTypeId BlurRelax::id(0x001226FD); - -// local attributes -MObject BlurRelax::aIterations; -MObject BlurRelax::aBorderBehavior; -MObject BlurRelax::aHardEdgeBehavior; -MObject BlurRelax::aGroupEdgeBehavior; -MObject BlurRelax::aReproject; -MObject BlurRelax::aTaubinBias; - - -BlurRelax::BlurRelax() {} -BlurRelax::~BlurRelax() {} - -void* BlurRelax::creator() { - return new BlurRelax(); -} - -MStatus BlurRelax::getTrueWeights(MObject &mesh, MDataBlock &dataBlock, UINT index, std::vector &weightVals, float envelope) const { - // Maya might do some tricky stuff like not store the weight array at all for certain weight - // values so we can't count on an array existing in the weightList. For the OpenCL Kernel - // we want an array with one weight in it per vertex, we need to build it carefully here. - - // Two possibilities: we could have a sparse array in weightList[multiIndex] or there could be nothing in weightList[multiIndex]. - // if nothing is there then all the weights at 1.0f. - - MStatus status; - MFnMesh meshFn(mesh); - UINT numVerts = meshFn.numVertices(); - weightVals.reserve(numVerts); - - // Get a handle to the weight array we want. - MArrayDataHandle weightList = dataBlock.outputArrayValue(MPxDeformerNode::weightList, &status); - if (!status) return status; // we should always be able to get a weightList - status = weightList.jumpToElement(index); - // it is possible that the jumpToElement fails. In that case use the envelope value - if (!status) { - weightVals.resize(numVerts, envelope); - } - else { - MDataHandle hWeightsStructure = weightList.inputValue(&status); - if (!status) return status; - MArrayDataHandle hWeights = hWeightsStructure.child(MPxDeformerNode::weights); - if (!status) return status; - - // number of non-zero weights - unsigned int numWeights = hWeights.elementCount(&status); - if (!status) return status; - - // we're building a list with a weight per vertex, even if the weight is zero - unsigned int weightIndex = 0; - for (unsigned int i = 0; i < numWeights; i++, hWeights.next()) { - unsigned int weightsElementIndex = hWeights.elementIndex(&status); - while (weightIndex < weightsElementIndex) { - weightVals.push_back(envelope); // weights could be sparse, fill in default weight if no data - weightIndex++; - } - MDataHandle value = hWeights.inputValue(&status); - weightVals.push_back(value.asFloat() * envelope); - weightIndex++; - } - // now we have written the last non-zero weight into weightVals, but the last non-zero weight - // doesn't have to be for the last vertex in the buffer. Add more default values if necessary. - weightVals.resize(numVerts, envelope); - } - return MStatus::kSuccess; -} - -MStatus BlurRelax::initialize() { - MStatus status; - MFnEnumAttribute eAttr; - MFnNumericAttribute nAttr; - - aBorderBehavior = eAttr.create("borderBehavior", "bb", BB_SLIDE, &status); - CHECKSTAT("aBorderBehavior"); - eAttr.setKeyable(false); - eAttr.setChannelBox(true); - status = eAttr.addField("None", BB_NONE); - CHECKSTAT("aBorderBehavior"); - status = eAttr.addField("Pin", BB_PIN); - CHECKSTAT("aBorderBehavior"); - status = eAttr.addField("Slide", BB_SLIDE); - CHECKSTAT("aBorderBehavior"); - status = addAttribute(aBorderBehavior); - CHECKSTAT("aBorderBehavior"); - status = attributeAffects(aBorderBehavior, outputGeom); - CHECKSTAT("aBorderBehavior"); - - aHardEdgeBehavior = eAttr.create("hardEdgeBehavior", "hb", HB_SLIDE, &status); - CHECKSTAT("aHardEdgeBehavior"); - eAttr.setKeyable(false); - eAttr.setChannelBox(true); - status = eAttr.addField("None", HB_NONE); - CHECKSTAT("aHardEdgeBehavior"); - status = eAttr.addField("Pin", HB_PIN); - CHECKSTAT("aHardEdgeBehavior"); - status = eAttr.addField("Slide", HB_SLIDE); - CHECKSTAT("aHardEdgeBehavior"); - status = addAttribute(aHardEdgeBehavior); - CHECKSTAT("aHardEdgeBehavior"); - status = attributeAffects(aHardEdgeBehavior, outputGeom); - CHECKSTAT("aHardEdgeBehavior"); - - aGroupEdgeBehavior = eAttr.create("groupEdgeBehavior", "gb", GB_PIN, &status); - CHECKSTAT("aGroupEdgeBehavior"); - eAttr.setKeyable(false); - eAttr.setChannelBox(true); - status = eAttr.addField("None", GB_NONE); - CHECKSTAT("aGroupEdgeBehavior"); - status = eAttr.addField("Pin", GB_PIN); - CHECKSTAT("aGroupEdgeBehavior"); - status = eAttr.addField("Slide", GB_SLIDE); - CHECKSTAT("aGroupEdgeBehavior"); - status = addAttribute(aGroupEdgeBehavior); - CHECKSTAT("aGroupEdgeBehavior"); - status = attributeAffects(aGroupEdgeBehavior, outputGeom); - CHECKSTAT("aGroupEdgeBehavior"); - - aReproject = nAttr.create("reproject", "rp", MFnNumericData::kBoolean, false, &status); - CHECKSTAT("aReproject"); - nAttr.setKeyable(false); - nAttr.setChannelBox(true); - status = addAttribute(aReproject); - CHECKSTAT("aReproject"); - status = attributeAffects(aReproject, outputGeom); - CHECKSTAT("aReproject"); - - // Taubin Bias is divided by 1000 internally - aTaubinBias = nAttr.create("preserveVolume", "pv", MFnNumericData::kFloat, 0.0f, &status); - CHECKSTAT("aTaubinBias"); - nAttr.setMin(0.0f); - nAttr.setMax(2.0f); - nAttr.setChannelBox(true); - status = addAttribute(aTaubinBias); - CHECKSTAT("aTaubinBias"); - status = attributeAffects(aTaubinBias, outputGeom); - CHECKSTAT("aTaubinBias"); - - aIterations = nAttr.create("iterations", "i", MFnNumericData::kFloat, 0, &status); - CHECKSTAT("aIterations"); - nAttr.setMin(0.0); - nAttr.setChannelBox(true); - status = addAttribute(aIterations); - CHECKSTAT("aIterations"); - status = attributeAffects(aIterations, outputGeom); - CHECKSTAT("aIterations"); - - MGlobal::executeCommand("makePaintable -attrType \"multiFloat\" -sm \"deformer\" \"" DEFORMER_NAME "\" \"weights\";"); - - return MStatus::kSuccess; -} - -MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MMatrix& matrix, UINT multiIndex) { - // Short circuit if the envelope is zero - MStatus status; - MDataHandle hEnv = dataBlock.inputValue(envelope); - float env = hEnv.asFloat(); - - MDataHandle hIter = dataBlock.inputValue(aIterations); - float iterations = hIter.asFloat(); - - if (iterations > 0 && env > 0.0f){ - // Get the data from the node - MDataHandle hBorder = dataBlock.inputValue(aBorderBehavior); - short bb = hBorder.asShort(); - MDataHandle hHardEdge = dataBlock.inputValue(aHardEdgeBehavior); - short hb = hHardEdge.asShort(); - MDataHandle hGroupEdge = dataBlock.inputValue(aGroupEdgeBehavior); - short gb = hGroupEdge.asShort(); - MDataHandle hReproject = dataBlock.inputValue(aReproject); - bool reproject = hReproject.asBool(); - - MDataHandle hTBias = dataBlock.inputValue(aTaubinBias); - float tBias = hTBias.asFloat(); - // volume preservation uses 2 steps per iteration - // so half the number of iterations if I'm volume preserving - // The iterations interpolating as floats takes care of 99% of the jumping - // There *will* be a tiny jump from 0.0 on preserve volume if - // reprojection is also turned on. I don't think I can get rid of that one - if (tBias > 0.0f) { - iterations /= 2.0f; - } - // So the numbers shown are intuitive to the user - // 0 maps to 1.0 and 1 maps to -1.05 - // -1.05 is used because taubin smoothing needs something - // just a little over 1.0 to truly preserve volume, - // and that value looked good on my test mesh. - tBias = -2.05f * tBias + 1.0f; - - // get the input mesh corresponding to this output - MObject thisNode = this->thisMObject(); - MPlug inPlug(thisNode, input); - inPlug.selectAncestorLogicalIndex(multiIndex, input); - MDataHandle hInput = dataBlock.inputValue(inPlug); - MObject mesh = hInput.asMesh(); - - // Get the point values - MFnMesh meshFn(mesh); - UINT numVerts = meshFn.numVertices(); - - // Get the group data - std::vector group; - group.resize(numVerts); - for (; !vertIter.isDone(); vertIter.next()) { - group[vertIter.index()] = true; - } - vertIter.reset(); - - // Get the true smooth-edge data - std::vector trueSmoothEdges; - trueSmoothEdges.resize(meshFn.numEdges()); - MItMeshEdge edgeIter(mesh); - for (; !edgeIter.isDone(); edgeIter.next()) { - if (edgeIter.isSmooth()) { - trueSmoothEdges[edgeIter.index()] = true; - } - } - edgeIter.reset(); - - // Hash the topology to see if we need to re-build the data - // I could be smarter about this and only recompute things when needed - // however, those re-computations wouldn't save frames when it mattered - MIntArray mfaceCounts, mvertIdxs; - int *faceCounts, *vertIdxs; - meshFn.getVertices(mfaceCounts, mvertIdxs); - faceCounts = new int[mfaceCounts.length()]; - vertIdxs = new int[mvertIdxs.length()]; - unsigned long long tFaceHash = XXH64(faceCounts, mfaceCounts.length()*sizeof(int), 0); - unsigned long long tEdgeHash = XXH64(vertIdxs, mvertIdxs.length()*sizeof(int), 0); - unsigned long long tGroupHash = XXH64(group.data(), group.size()*sizeof(char), 0); - unsigned long long tSmoothHash = XXH64(trueSmoothEdges.data(), trueSmoothEdges.size()*sizeof(char), 0); - delete[] faceCounts; - delete[] vertIdxs; - - if ( - (bbCheck != bb) || (hbCheck != hb) || (gbCheck != gb) || - (tFaceHash != faceHash) || (tEdgeHash != edgeHash) || - (tGroupHash != groupHash) || (tSmoothHash != smoothHash) - ) { - bbCheck = bb; hbCheck = hb; gbCheck = gb; - faceHash = tFaceHash; edgeHash = tEdgeHash; - groupHash = tGroupHash; smoothHash = tSmoothHash; - - // Populate the variables with *SPECIALLY ORDERED* data - // all vertex data is now shuffled by the order vector - buildQuickData(mesh, vertIter, bb, hb, gb, reproject, group); - } - - // This can happen if the user is pinning all the points - // or all the edges are hard (like when you import an obj) - if (neighbors.empty()) { - return status; - } - - // Build the raw float data buffers - pointArray_t mpa; - float_t(*reoVerts)[4] = new float_t[numVerts][4]; - meshFn.getPoints(mpa); - - for (size_t i = 0; i < numVerts; ++i) { - reoVerts[i][0] = mpa[order[i]].x; - reoVerts[i][1] = mpa[order[i]].y; - reoVerts[i][2] = mpa[order[i]].z; - reoVerts[i][3] = mpa[order[i]].w; - } - - // Calculate the relax, and store in verts - quickRelax(mesh, bb, hb, gb, reproject, tBias, iterations, numVerts, group, reoVerts); - - // Get the painted weight values - std::vector weightVals; - status = getTrueWeights(mesh, dataBlock, multiIndex, weightVals, env); - - // Finally set the output - for (; !vertIter.isDone(); vertIter.next()) { - const UINT idx = vertIter.index(); - vertIter.setPosition((weightVals[idx]) * point_t(reoVerts[invOrder[idx]]) + (1.0 - weightVals[idx]) * vertIter.position()); - } - - // Make sure to clean up after myself - delete [] reoVerts; - } - return status; -} - -MStatus initializePlugin(MObject obj) { - MStatus result; - MFnPlugin plugin(obj, "Blur Studio", "1.0", "Any"); - result = plugin.registerNode(DEFORMER_NAME, BlurRelax::id, BlurRelax::creator, BlurRelax::initialize, MPxNode::kDeformerNode); - - return result; -} - -MStatus uninitializePlugin(MObject obj) { - MStatus result; - MFnPlugin plugin(obj); - result = plugin.deregisterNode(BlurRelax::id); - return result; -} - -void BlurRelax::buildQuickData( - MObject &mesh, - MItGeometry& vertIter, - short borderBehavior, - short hardEdgeBehavior, - short groupEdgeBehavior, - bool reproject, - std::vector &group -) { - // This takes the mesh, Gets all the required data, reorders so - // the verts are in descending order of valence, and gets the subgroup - // I'm trying to pre-process everything I can right here so I don't have to - // branch in quickLaplacianSmooth() so auto-vectorization works - MFnMesh meshFn(mesh); - UINT numVertices = meshFn.numVertices(); - - std::vector rawPinPoints; - std::vector rawCreaseCount; - rawCreaseCount.resize(numVertices); - rawPinPoints.resize(numVertices); - - std::vector groupBorders; - std::vector isGroupBorder; - isGroupBorder.resize(numVertices); - - std::vector> rawNeighbors; - std::vector> rawHardEdges; - std::vector> rawPinBorders; - rawHardEdges.resize(numVertices); - rawNeighbors.resize(numVertices); - - // Get the connectivity data - - // Instead of treating neighbors as an un-directed graph, I treat it as a directed graph - // This way I can remove influences from vertices, but not their neighbors - // So I can remove all neighbors from a vertex to pin it, but its neighbors can still use it as an influence. - // The big trick is that all that pinning computation only happens once, right here. - // And after all this junk, I only have to apply one set of very simple, very optimizable rules - - MItMeshEdge edgeIter(mesh); - for (; !edgeIter.isDone(); edgeIter.next()) { - const UINT start = edgeIter.index(0); - const UINT end = edgeIter.index(1); - // Hard means the edge has a special behavior applied to it - // Pinned means that special behavior is to not move - // hard and unpinned means slide if there are exactly 2 connected hard edges - // hard and unpinned means pin otherwise - // TODO: Also handle *IGNORED* neighbors - const bool onBound = edgeIter.onBoundary(); - const bool isHard = !edgeIter.isSmooth(); - const bool hard = (onBound && borderBehavior) || (isHard && !onBound && hardEdgeBehavior); - const bool pin = ((borderBehavior == BB_PIN) && onBound) || ((hardEdgeBehavior == HB_PIN) && isHard && !onBound); - - const bool startInGroup = group[start]; - const bool endInGroup = group[end]; - if (!startInGroup && !endInGroup) continue; - - if (startInGroup && endInGroup) { - if (pin) { - rawPinPoints[start] = true; - rawPinPoints[end] = true; - } - else { - rawNeighbors[start].push_back(end); - rawHardEdges[start].push_back(hard); - rawNeighbors[end].push_back(start); - rawHardEdges[end].push_back(hard); - if (hard) { - ++rawCreaseCount[start]; - ++rawCreaseCount[end]; - } - } - } - else if (!startInGroup) { - groupBorders.push_back(end); - isGroupBorder[end] = true; - } - else if (!endInGroup) { - groupBorders.push_back(start); - isGroupBorder[start] = true; - } - } - - const bool gbHard = groupEdgeBehavior; - const bool gbPin = groupEdgeBehavior == GB_PIN; - for (UINT gb: groupBorders){ - const auto nnn = rawNeighbors[gb]; - for (size_t i = 0; i < nnn.size(); ++i) { - const UINT n = nnn[i]; - if (isGroupBorder[n] ){ - rawHardEdges[gb][i] = rawHardEdges[gb][i] || gbHard; - if (gbPin){ - rawPinPoints[gb] = true; - rawPinPoints[n] = true; - } - else if (gbHard && (gb > n)){ - // Each edge this way is visited twice - // So check (gb>n) to only increment once - ++rawCreaseCount[gb]; - ++rawCreaseCount[n]; - } - } - } - } - - // if a vert has hard neighbors, remove all non-hard neighbors - // if a vert is pinned remove all of its neighbors - // if a vert has a pinned neighbor, remove it - - std::vector rawShiftVal; - rawShiftVal.resize(numVertices); - std::fill(rawShiftVal.begin(), rawShiftVal.end(), 0.5); - - for (size_t i = 0; i < rawNeighbors.size(); ++i) { - if ((rawCreaseCount[i] != 0) || rawPinPoints[i]) { - if (rawCreaseCount[i] != 2) - rawPinPoints[i] = true; - - std::vector newNeigh; - std::vector newHard; - if (!rawPinPoints[i]) { - for (size_t j = 0; j < rawNeighbors[i].size(); ++j) { - if (rawHardEdges[i][j]) { - newNeigh.push_back(rawNeighbors[i][j]); - newHard.push_back(rawHardEdges[i][j]); - } - } - } - rawNeighbors[i] = newNeigh; - rawHardEdges[i] = newHard; - rawShiftVal[i] = 0.25; - } - } - - // Sort the verts by their valence count - order.resize(numVertices); - std::iota(order.begin(), order.end(), 0u); // like python range() - - // compare using > for a sort from high to low - std::sort(order.begin(), order.end(), [&rawNeighbors](size_t a, size_t b) {return rawNeighbors[a].size() > rawNeighbors[b].size(); }); - - // Build the "transposed" neighbor and hard edge values - size_t maxValence = rawNeighbors[order[0]].size(); - neighbors.clear(); - neighbors.resize(maxValence); - hardEdges.clear(); - hardEdges.resize(maxValence); - creaseCount.resize(numVertices); - pinPoints.resize(numVertices); - - valence.resize(numVertices*4); - shiftVal.resize(numVertices*4); - - invOrder.resize(order.size()); - - for (size_t i = 0; i < order.size(); ++i) { - invOrder[order[i]] = i; - } - - for (size_t i = 0; i < numVertices; ++i) { - const auto &neigh = rawNeighbors[order[i]]; - const auto &hards = rawHardEdges[order[i]]; - size_t vale = neigh.size(); - for (size_t n = 0; n < vale; ++n) { - neighbors[n].push_back(invOrder[neigh[n]]); - hardEdges[n].push_back(invOrder[hards[n]]); - } - creaseCount[i] = rawCreaseCount[order[i]]; - pinPoints[i] = rawPinPoints[order[i]]; - - // Vectorizing flattens the vert list, so - // I need these per vert, per component - for (size_t xx = 0; xx < 4; ++xx) { - valence[4 * i + xx] = float_t(vale); - shiftVal[4 * i + xx] = rawShiftVal[order[i]]; - } - } -} - -void BlurRelax::quickRelax( - MObject &mesh, - const short borderBehavior, - const short hardEdgeBehavior, - const short groupEdgeBehavior, - const bool reproject, - const float taubinBias, - const float_t iterations, - const UINT numVerts, - const std::vector &group, - float_t(*verts)[4] -) { - bool rpEdges = (borderBehavior == BB_SLIDE) || (hardEdgeBehavior == HB_SLIDE) || (groupEdgeBehavior == GB_SLIDE); - std::vector groupIdxs; - - float_t (*baseVerts)[4]; - if (rpEdges) { - for (size_t i = 0; i < group.size(); ++i) { - if (group[i]) groupIdxs.push_back(i); - } - // make a copy of the original verts only if they'll be used for edge reprojection - baseVerts = new float_t[numVerts][4]; - memcpy(&(baseVerts[0][0]), &(verts[0][0]), 4 * numVerts * sizeof(float_t)); - } - - float_t(*prevVerts)[4]; - prevVerts = new float_t[numVerts][4]; - - float_t iterT, iterFI; - iterT = modf(iterations, &iterFI); - UINT iterI = (UINT)iterFI; - if (iterT > 0.0) { - iterI += 1; - } - - size_t nonzeroValence = neighbors[0].size(); - - MStatus status; - MMeshIntersector octree; - MObject smoothMeshPar, smoothMesh; - MFnMesh meshFn(mesh); - if (reproject) { - MFnMeshData smoothMeshParFn; - MMeshSmoothOptions smoothOpt; - smoothMeshPar = smoothMeshParFn.create(); - smoothOpt.setDivisions(1); - smoothOpt.setKeepBorderEdge(rpEdges); - smoothOpt.setSubdivisionType(MMeshSmoothOptions::kCatmullClark); - smoothMesh = meshFn.generateSmoothMesh(smoothMeshPar, &smoothOpt); - octree.create(smoothMesh); - } - - for (size_t r = 0; r < iterI; ++r) { - if ((r == iterI - 1) && (iterT > 0.0)){ - // Store the next-to-last iteration to interpolate with - memcpy(&(prevVerts[0][0]), &(verts[0][0]), 4 * numVerts * sizeof(float_t)); - } - quickLaplacianSmooth(verts, numVerts, neighbors, valence, shiftVal, pinPoints); - if (taubinBias < 1.0){ - quickLaplacianSmooth(verts, numVerts, neighbors, valence, shiftVal, pinPoints, taubinBias); - } - - if (rpEdges) { - edgeProject(baseVerts, groupIdxs, invOrder, neighbors, hardEdges, creaseCount, verts); - } - - if (reproject) { - #pragma omp parallel for if(numVerts>2000) - for (int i = 0; i < nonzeroValence; ++i) { - if ((creaseCount[i] == 0) && (group[order[i]])) { - point_t mf(verts[i][0], verts[i][1], verts[i][2]); - MPointOnMesh pom; - octree.getClosestPoint(mf, pom); - point_t gpf = pom.getPoint(); - //mfp.set(gpf, i); - verts[i][0] = gpf[0]; - verts[i][1] = gpf[1]; - verts[i][2] = gpf[2]; - } - } - } - } - - // Interpolate between prevVerts and verts based on iterT - if (iterT > 0.0) { - // This should vectorize - float_t * vv = &verts[0][0]; - float_t * pv = &prevVerts[0][0]; - for (size_t i = 0; i < numVerts * 4; ++i) { - vv[i] = ((vv[i] - pv[i]) * iterT) + pv[i]; - } - } - - if (rpEdges) delete[] baseVerts; - delete[] prevVerts; -} - diff --git a/external/xxhash.c b/external/xxhash.c deleted file mode 100644 index da06ea7..0000000 --- a/external/xxhash.c +++ /dev/null @@ -1,1029 +0,0 @@ -/* -* xxHash - Fast Hash algorithm -* Copyright (C) 2012-2016, Yann Collet -* -* BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are -* met: -* -* * Redistributions of source code must retain the above copyright -* notice, this list of conditions and the following disclaimer. -* * Redistributions in binary form must reproduce the above -* copyright notice, this list of conditions and the following disclaimer -* in the documentation and/or other materials provided with the -* distribution. -* -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -* -* You can contact the author at : -* - xxHash homepage: http://www.xxhash.com -* - xxHash source repository : https://github.com/Cyan4973/xxHash -*/ - - -/* ************************************* -* Tuning parameters -***************************************/ -/*!XXH_FORCE_MEMORY_ACCESS : - * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. - * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. - * The below switch allow to select different access method for improved performance. - * Method 0 (default) : use `memcpy()`. Safe and portable. - * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable). - * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. - * Method 2 : direct access. This method doesn't depend on compiler but violate C standard. - * It can generate buggy code on targets which do not support unaligned memory accesses. - * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6) - * See http://stackoverflow.com/a/32095106/646947 for details. - * Prefer these methods in priority order (0 > 1 > 2) - */ -#ifndef XXH_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */ -# if defined(__GNUC__) && ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) \ - || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) \ - || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) -# define XXH_FORCE_MEMORY_ACCESS 2 -# elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || \ - (defined(__GNUC__) && ( defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) \ - || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) \ - || defined(__ARM_ARCH_7S__) )) -# define XXH_FORCE_MEMORY_ACCESS 1 -# endif -#endif - -/*!XXH_ACCEPT_NULL_INPUT_POINTER : - * If input pointer is NULL, xxHash default behavior is to dereference it, triggering a segfault. - * When this macro is enabled, xxHash actively checks input for null pointer. - * It it is, result for null input pointers is the same as a null-length input. - */ -#ifndef XXH_ACCEPT_NULL_INPUT_POINTER /* can be defined externally */ -# define XXH_ACCEPT_NULL_INPUT_POINTER 0 -#endif - -/*!XXH_FORCE_NATIVE_FORMAT : - * By default, xxHash library provides endian-independent Hash values, based on little-endian convention. - * Results are therefore identical for little-endian and big-endian CPU. - * This comes at a performance cost for big-endian CPU, since some swapping is required to emulate little-endian format. - * Should endian-independence be of no importance for your application, you may set the #define below to 1, - * to improve speed for Big-endian CPU. - * This option has no impact on Little_Endian CPU. - */ -#ifndef XXH_FORCE_NATIVE_FORMAT /* can be defined externally */ -# define XXH_FORCE_NATIVE_FORMAT 0 -#endif - -/*!XXH_FORCE_ALIGN_CHECK : - * This is a minor performance trick, only useful with lots of very small keys. - * It means : check for aligned/unaligned input. - * The check costs one initial branch per hash; - * set it to 0 when the input is guaranteed to be aligned, - * or when alignment doesn't matter for performance. - */ -#ifndef XXH_FORCE_ALIGN_CHECK /* can be defined externally */ -# if defined(__i386) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64) -# define XXH_FORCE_ALIGN_CHECK 0 -# else -# define XXH_FORCE_ALIGN_CHECK 1 -# endif -#endif - - -/* ************************************* -* Includes & Memory related functions -***************************************/ -/*! Modify the local functions below should you wish to use some other memory routines -* for malloc(), free() */ -#include -static void* XXH_malloc(size_t s) { return malloc(s); } -static void XXH_free (void* p) { free(p); } -/*! and for memcpy() */ -#include -static void* XXH_memcpy(void* dest, const void* src, size_t size) { return memcpy(dest,src,size); } - -#include /* assert */ - -#define XXH_STATIC_LINKING_ONLY -#include "xxhash.h" - - -/* ************************************* -* Compiler Specific Options -***************************************/ -#ifdef _MSC_VER /* Visual Studio */ -# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ -# define FORCE_INLINE static __forceinline -#else -# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ -# ifdef __GNUC__ -# define FORCE_INLINE static inline __attribute__((always_inline)) -# else -# define FORCE_INLINE static inline -# endif -# else -# define FORCE_INLINE static -# endif /* __STDC_VERSION__ */ -#endif - - -/* ************************************* -* Basic Types -***************************************/ -#ifndef MEM_MODULE -# if !defined (__VMS) \ - && (defined (__cplusplus) \ - || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) -# include - typedef uint8_t BYTE; - typedef uint16_t U16; - typedef uint32_t U32; -# else - typedef unsigned char BYTE; - typedef unsigned short U16; - typedef unsigned int U32; -# endif -#endif - -#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2)) - -/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */ -static U32 XXH_read32(const void* memPtr) { return *(const U32*) memPtr; } - -#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1)) - -/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ -/* currently only defined for gcc and icc */ -typedef union { U32 u32; } __attribute__((packed)) unalign; -static U32 XXH_read32(const void* ptr) { return ((const unalign*)ptr)->u32; } - -#else - -/* portable and safe solution. Generally efficient. - * see : http://stackoverflow.com/a/32095106/646947 - */ -static U32 XXH_read32(const void* memPtr) -{ - U32 val; - memcpy(&val, memPtr, sizeof(val)); - return val; -} - -#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */ - - -/* **************************************** -* Compiler-specific Functions and Macros -******************************************/ -#define XXH_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) - -/* Note : although _rotl exists for minGW (GCC under windows), performance seems poor */ -#if defined(_MSC_VER) -# define XXH_rotl32(x,r) _rotl(x,r) -# define XXH_rotl64(x,r) _rotl64(x,r) -#else -# define XXH_rotl32(x,r) ((x << r) | (x >> (32 - r))) -# define XXH_rotl64(x,r) ((x << r) | (x >> (64 - r))) -#endif - -#if defined(_MSC_VER) /* Visual Studio */ -# define XXH_swap32 _byteswap_ulong -#elif XXH_GCC_VERSION >= 403 -# define XXH_swap32 __builtin_bswap32 -#else -static U32 XXH_swap32 (U32 x) -{ - return ((x << 24) & 0xff000000 ) | - ((x << 8) & 0x00ff0000 ) | - ((x >> 8) & 0x0000ff00 ) | - ((x >> 24) & 0x000000ff ); -} -#endif - - -/* ************************************* -* Architecture Macros -***************************************/ -typedef enum { XXH_bigEndian=0, XXH_littleEndian=1 } XXH_endianess; - -/* XXH_CPU_LITTLE_ENDIAN can be defined externally, for example on the compiler command line */ -#ifndef XXH_CPU_LITTLE_ENDIAN -static int XXH_isLittleEndian(void) -{ - const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ - return one.c[0]; -} -# define XXH_CPU_LITTLE_ENDIAN XXH_isLittleEndian() -#endif - - -/* *************************** -* Memory reads -*****************************/ -typedef enum { XXH_aligned, XXH_unaligned } XXH_alignment; - -FORCE_INLINE U32 XXH_readLE32_align(const void* ptr, XXH_endianess endian, XXH_alignment align) -{ - if (align==XXH_unaligned) - return endian==XXH_littleEndian ? XXH_read32(ptr) : XXH_swap32(XXH_read32(ptr)); - else - return endian==XXH_littleEndian ? *(const U32*)ptr : XXH_swap32(*(const U32*)ptr); -} - -FORCE_INLINE U32 XXH_readLE32(const void* ptr, XXH_endianess endian) -{ - return XXH_readLE32_align(ptr, endian, XXH_unaligned); -} - -static U32 XXH_readBE32(const void* ptr) -{ - return XXH_CPU_LITTLE_ENDIAN ? XXH_swap32(XXH_read32(ptr)) : XXH_read32(ptr); -} - - -/* ************************************* -* Macros -***************************************/ -#define XXH_STATIC_ASSERT(c) { enum { XXH_sa = 1/(int)(!!(c)) }; } /* use after variable declarations */ -XXH_PUBLIC_API unsigned XXH_versionNumber (void) { return XXH_VERSION_NUMBER; } - - -/* ******************************************************************* -* 32-bit hash functions -*********************************************************************/ -static const U32 PRIME32_1 = 2654435761U; -static const U32 PRIME32_2 = 2246822519U; -static const U32 PRIME32_3 = 3266489917U; -static const U32 PRIME32_4 = 668265263U; -static const U32 PRIME32_5 = 374761393U; - -static U32 XXH32_round(U32 seed, U32 input) -{ - seed += input * PRIME32_2; - seed = XXH_rotl32(seed, 13); - seed *= PRIME32_1; - return seed; -} - -/* mix all bits */ -static U32 XXH32_avalanche(U32 h32) -{ - h32 ^= h32 >> 15; - h32 *= PRIME32_2; - h32 ^= h32 >> 13; - h32 *= PRIME32_3; - h32 ^= h32 >> 16; - return(h32); -} - -#define XXH_get32bits(p) XXH_readLE32_align(p, endian, align) - -static U32 -XXH32_finalize(U32 h32, const void* ptr, size_t len, - XXH_endianess endian, XXH_alignment align) - -{ - const BYTE* p = (const BYTE*)ptr; -#define PROCESS1 \ - h32 += (*p) * PRIME32_5; \ - p++; \ - h32 = XXH_rotl32(h32, 11) * PRIME32_1 ; - -#define PROCESS4 \ - h32 += XXH_get32bits(p) * PRIME32_3; \ - p+=4; \ - h32 = XXH_rotl32(h32, 17) * PRIME32_4 ; - - switch(len&15) /* or switch(bEnd - p) */ - { - case 12: PROCESS4; - /* fallthrough */ - case 8: PROCESS4; - /* fallthrough */ - case 4: PROCESS4; - return XXH32_avalanche(h32); - - case 13: PROCESS4; - /* fallthrough */ - case 9: PROCESS4; - /* fallthrough */ - case 5: PROCESS4; - PROCESS1; - return XXH32_avalanche(h32); - - case 14: PROCESS4; - /* fallthrough */ - case 10: PROCESS4; - /* fallthrough */ - case 6: PROCESS4; - PROCESS1; - PROCESS1; - return XXH32_avalanche(h32); - - case 15: PROCESS4; - /* fallthrough */ - case 11: PROCESS4; - /* fallthrough */ - case 7: PROCESS4; - /* fallthrough */ - case 3: PROCESS1; - /* fallthrough */ - case 2: PROCESS1; - /* fallthrough */ - case 1: PROCESS1; - /* fallthrough */ - case 0: return XXH32_avalanche(h32); - } - assert(0); - return h32; /* reaching this point is deemed impossible */ -} - - -FORCE_INLINE U32 -XXH32_endian_align(const void* input, size_t len, U32 seed, - XXH_endianess endian, XXH_alignment align) -{ - const BYTE* p = (const BYTE*)input; - const BYTE* bEnd = p + len; - U32 h32; - -#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) - if (p==NULL) { - len=0; - bEnd=p=(const BYTE*)(size_t)16; - } -#endif - - if (len>=16) { - const BYTE* const limit = bEnd - 15; - U32 v1 = seed + PRIME32_1 + PRIME32_2; - U32 v2 = seed + PRIME32_2; - U32 v3 = seed + 0; - U32 v4 = seed - PRIME32_1; - - do { - v1 = XXH32_round(v1, XXH_get32bits(p)); p+=4; - v2 = XXH32_round(v2, XXH_get32bits(p)); p+=4; - v3 = XXH32_round(v3, XXH_get32bits(p)); p+=4; - v4 = XXH32_round(v4, XXH_get32bits(p)); p+=4; - } while (p < limit); - - h32 = XXH_rotl32(v1, 1) + XXH_rotl32(v2, 7) - + XXH_rotl32(v3, 12) + XXH_rotl32(v4, 18); - } else { - h32 = seed + PRIME32_5; - } - - h32 += (U32)len; - - return XXH32_finalize(h32, p, len&15, endian, align); -} - - -XXH_PUBLIC_API unsigned int XXH32 (const void* input, size_t len, unsigned int seed) -{ -#if 0 - /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ - XXH32_state_t state; - XXH32_reset(&state, seed); - XXH32_update(&state, input, len); - return XXH32_digest(&state); -#else - XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; - - if (XXH_FORCE_ALIGN_CHECK) { - if ((((size_t)input) & 3) == 0) { /* Input is 4-bytes aligned, leverage the speed benefit */ - if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) - return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned); - else - return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned); - } } - - if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) - return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned); - else - return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned); -#endif -} - - - -/*====== Hash streaming ======*/ - -XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void) -{ - return (XXH32_state_t*)XXH_malloc(sizeof(XXH32_state_t)); -} -XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr) -{ - XXH_free(statePtr); - return XXH_OK; -} - -XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dstState, const XXH32_state_t* srcState) -{ - memcpy(dstState, srcState, sizeof(*dstState)); -} - -XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, unsigned int seed) -{ - XXH32_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */ - memset(&state, 0, sizeof(state)); - state.v1 = seed + PRIME32_1 + PRIME32_2; - state.v2 = seed + PRIME32_2; - state.v3 = seed + 0; - state.v4 = seed - PRIME32_1; - /* do not write into reserved, planned to be removed in a future version */ - memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved)); - return XXH_OK; -} - - -FORCE_INLINE -XXH_errorcode XXH32_update_endian (XXH32_state_t* state, const void* input, size_t len, XXH_endianess endian) -{ - const BYTE* p = (const BYTE*)input; - const BYTE* const bEnd = p + len; - - if (input==NULL) -#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) - return XXH_OK; -#else - return XXH_ERROR; -#endif - - state->total_len_32 += (unsigned)len; - state->large_len |= (len>=16) | (state->total_len_32>=16); - - if (state->memsize + len < 16) { /* fill in tmp buffer */ - XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, len); - state->memsize += (unsigned)len; - return XXH_OK; - } - - if (state->memsize) { /* some data left from previous update */ - XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, 16-state->memsize); - { const U32* p32 = state->mem32; - state->v1 = XXH32_round(state->v1, XXH_readLE32(p32, endian)); p32++; - state->v2 = XXH32_round(state->v2, XXH_readLE32(p32, endian)); p32++; - state->v3 = XXH32_round(state->v3, XXH_readLE32(p32, endian)); p32++; - state->v4 = XXH32_round(state->v4, XXH_readLE32(p32, endian)); - } - p += 16-state->memsize; - state->memsize = 0; - } - - if (p <= bEnd-16) { - const BYTE* const limit = bEnd - 16; - U32 v1 = state->v1; - U32 v2 = state->v2; - U32 v3 = state->v3; - U32 v4 = state->v4; - - do { - v1 = XXH32_round(v1, XXH_readLE32(p, endian)); p+=4; - v2 = XXH32_round(v2, XXH_readLE32(p, endian)); p+=4; - v3 = XXH32_round(v3, XXH_readLE32(p, endian)); p+=4; - v4 = XXH32_round(v4, XXH_readLE32(p, endian)); p+=4; - } while (p<=limit); - - state->v1 = v1; - state->v2 = v2; - state->v3 = v3; - state->v4 = v4; - } - - if (p < bEnd) { - XXH_memcpy(state->mem32, p, (size_t)(bEnd-p)); - state->memsize = (unsigned)(bEnd-p); - } - - return XXH_OK; -} - - -XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* state_in, const void* input, size_t len) -{ - XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; - - if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) - return XXH32_update_endian(state_in, input, len, XXH_littleEndian); - else - return XXH32_update_endian(state_in, input, len, XXH_bigEndian); -} - - -FORCE_INLINE U32 -XXH32_digest_endian (const XXH32_state_t* state, XXH_endianess endian) -{ - U32 h32; - - if (state->large_len) { - h32 = XXH_rotl32(state->v1, 1) - + XXH_rotl32(state->v2, 7) - + XXH_rotl32(state->v3, 12) - + XXH_rotl32(state->v4, 18); - } else { - h32 = state->v3 /* == seed */ + PRIME32_5; - } - - h32 += state->total_len_32; - - return XXH32_finalize(h32, state->mem32, state->memsize, endian, XXH_aligned); -} - - -XXH_PUBLIC_API unsigned int XXH32_digest (const XXH32_state_t* state_in) -{ - XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; - - if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) - return XXH32_digest_endian(state_in, XXH_littleEndian); - else - return XXH32_digest_endian(state_in, XXH_bigEndian); -} - - -/*====== Canonical representation ======*/ - -/*! Default XXH result types are basic unsigned 32 and 64 bits. -* The canonical representation follows human-readable write convention, aka big-endian (large digits first). -* These functions allow transformation of hash result into and from its canonical format. -* This way, hash values can be written into a file or buffer, remaining comparable across different systems. -*/ - -XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash) -{ - XXH_STATIC_ASSERT(sizeof(XXH32_canonical_t) == sizeof(XXH32_hash_t)); - if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap32(hash); - memcpy(dst, &hash, sizeof(*dst)); -} - -XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src) -{ - return XXH_readBE32(src); -} - - -#ifndef XXH_NO_LONG_LONG - -/* ******************************************************************* -* 64-bit hash functions -*********************************************************************/ - -/*====== Memory access ======*/ - -#ifndef MEM_MODULE -# define MEM_MODULE -# if !defined (__VMS) \ - && (defined (__cplusplus) \ - || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) -# include - typedef uint64_t U64; -# else - /* if compiler doesn't support unsigned long long, replace by another 64-bit type */ - typedef unsigned long long U64; -# endif -#endif - - -#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2)) - -/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */ -static U64 XXH_read64(const void* memPtr) { return *(const U64*) memPtr; } - -#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1)) - -/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ -/* currently only defined for gcc and icc */ -typedef union { U32 u32; U64 u64; } __attribute__((packed)) unalign64; -static U64 XXH_read64(const void* ptr) { return ((const unalign64*)ptr)->u64; } - -#else - -/* portable and safe solution. Generally efficient. - * see : http://stackoverflow.com/a/32095106/646947 - */ - -static U64 XXH_read64(const void* memPtr) -{ - U64 val; - memcpy(&val, memPtr, sizeof(val)); - return val; -} - -#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */ - -#if defined(_MSC_VER) /* Visual Studio */ -# define XXH_swap64 _byteswap_uint64 -#elif XXH_GCC_VERSION >= 403 -# define XXH_swap64 __builtin_bswap64 -#else -static U64 XXH_swap64 (U64 x) -{ - return ((x << 56) & 0xff00000000000000ULL) | - ((x << 40) & 0x00ff000000000000ULL) | - ((x << 24) & 0x0000ff0000000000ULL) | - ((x << 8) & 0x000000ff00000000ULL) | - ((x >> 8) & 0x00000000ff000000ULL) | - ((x >> 24) & 0x0000000000ff0000ULL) | - ((x >> 40) & 0x000000000000ff00ULL) | - ((x >> 56) & 0x00000000000000ffULL); -} -#endif - -FORCE_INLINE U64 XXH_readLE64_align(const void* ptr, XXH_endianess endian, XXH_alignment align) -{ - if (align==XXH_unaligned) - return endian==XXH_littleEndian ? XXH_read64(ptr) : XXH_swap64(XXH_read64(ptr)); - else - return endian==XXH_littleEndian ? *(const U64*)ptr : XXH_swap64(*(const U64*)ptr); -} - -FORCE_INLINE U64 XXH_readLE64(const void* ptr, XXH_endianess endian) -{ - return XXH_readLE64_align(ptr, endian, XXH_unaligned); -} - -static U64 XXH_readBE64(const void* ptr) -{ - return XXH_CPU_LITTLE_ENDIAN ? XXH_swap64(XXH_read64(ptr)) : XXH_read64(ptr); -} - - -/*====== xxh64 ======*/ - -static const U64 PRIME64_1 = 11400714785074694791ULL; -static const U64 PRIME64_2 = 14029467366897019727ULL; -static const U64 PRIME64_3 = 1609587929392839161ULL; -static const U64 PRIME64_4 = 9650029242287828579ULL; -static const U64 PRIME64_5 = 2870177450012600261ULL; - -static U64 XXH64_round(U64 acc, U64 input) -{ - acc += input * PRIME64_2; - acc = XXH_rotl64(acc, 31); - acc *= PRIME64_1; - return acc; -} - -static U64 XXH64_mergeRound(U64 acc, U64 val) -{ - val = XXH64_round(0, val); - acc ^= val; - acc = acc * PRIME64_1 + PRIME64_4; - return acc; -} - -static U64 XXH64_avalanche(U64 h64) -{ - h64 ^= h64 >> 33; - h64 *= PRIME64_2; - h64 ^= h64 >> 29; - h64 *= PRIME64_3; - h64 ^= h64 >> 32; - return h64; -} - - -#define XXH_get64bits(p) XXH_readLE64_align(p, endian, align) - -static U64 -XXH64_finalize(U64 h64, const void* ptr, size_t len, - XXH_endianess endian, XXH_alignment align) -{ - const BYTE* p = (const BYTE*)ptr; - -#define PROCESS1_64 \ - h64 ^= (*p) * PRIME64_5; \ - p++; \ - h64 = XXH_rotl64(h64, 11) * PRIME64_1; - -#define PROCESS4_64 \ - h64 ^= (U64)(XXH_get32bits(p)) * PRIME64_1; \ - p+=4; \ - h64 = XXH_rotl64(h64, 23) * PRIME64_2 + PRIME64_3; - -#define PROCESS8_64 { \ - U64 const k1 = XXH64_round(0, XXH_get64bits(p)); \ - p+=8; \ - h64 ^= k1; \ - h64 = XXH_rotl64(h64,27) * PRIME64_1 + PRIME64_4; \ -} - - switch(len&31) { - case 24: PROCESS8_64; - /* fallthrough */ - case 16: PROCESS8_64; - /* fallthrough */ - case 8: PROCESS8_64; - return XXH64_avalanche(h64); - - case 28: PROCESS8_64; - /* fallthrough */ - case 20: PROCESS8_64; - /* fallthrough */ - case 12: PROCESS8_64; - /* fallthrough */ - case 4: PROCESS4_64; - return XXH64_avalanche(h64); - - case 25: PROCESS8_64; - /* fallthrough */ - case 17: PROCESS8_64; - /* fallthrough */ - case 9: PROCESS8_64; - PROCESS1_64; - return XXH64_avalanche(h64); - - case 29: PROCESS8_64; - /* fallthrough */ - case 21: PROCESS8_64; - /* fallthrough */ - case 13: PROCESS8_64; - /* fallthrough */ - case 5: PROCESS4_64; - PROCESS1_64; - return XXH64_avalanche(h64); - - case 26: PROCESS8_64; - /* fallthrough */ - case 18: PROCESS8_64; - /* fallthrough */ - case 10: PROCESS8_64; - PROCESS1_64; - PROCESS1_64; - return XXH64_avalanche(h64); - - case 30: PROCESS8_64; - /* fallthrough */ - case 22: PROCESS8_64; - /* fallthrough */ - case 14: PROCESS8_64; - /* fallthrough */ - case 6: PROCESS4_64; - PROCESS1_64; - PROCESS1_64; - return XXH64_avalanche(h64); - - case 27: PROCESS8_64; - /* fallthrough */ - case 19: PROCESS8_64; - /* fallthrough */ - case 11: PROCESS8_64; - PROCESS1_64; - PROCESS1_64; - PROCESS1_64; - return XXH64_avalanche(h64); - - case 31: PROCESS8_64; - /* fallthrough */ - case 23: PROCESS8_64; - /* fallthrough */ - case 15: PROCESS8_64; - /* fallthrough */ - case 7: PROCESS4_64; - /* fallthrough */ - case 3: PROCESS1_64; - /* fallthrough */ - case 2: PROCESS1_64; - /* fallthrough */ - case 1: PROCESS1_64; - /* fallthrough */ - case 0: return XXH64_avalanche(h64); - } - - /* impossible to reach */ - assert(0); - return 0; /* unreachable, but some compilers complain without it */ -} - -FORCE_INLINE U64 -XXH64_endian_align(const void* input, size_t len, U64 seed, - XXH_endianess endian, XXH_alignment align) -{ - const BYTE* p = (const BYTE*)input; - const BYTE* bEnd = p + len; - U64 h64; - -#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) - if (p==NULL) { - len=0; - bEnd=p=(const BYTE*)(size_t)32; - } -#endif - - if (len>=32) { - const BYTE* const limit = bEnd - 32; - U64 v1 = seed + PRIME64_1 + PRIME64_2; - U64 v2 = seed + PRIME64_2; - U64 v3 = seed + 0; - U64 v4 = seed - PRIME64_1; - - do { - v1 = XXH64_round(v1, XXH_get64bits(p)); p+=8; - v2 = XXH64_round(v2, XXH_get64bits(p)); p+=8; - v3 = XXH64_round(v3, XXH_get64bits(p)); p+=8; - v4 = XXH64_round(v4, XXH_get64bits(p)); p+=8; - } while (p<=limit); - - h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18); - h64 = XXH64_mergeRound(h64, v1); - h64 = XXH64_mergeRound(h64, v2); - h64 = XXH64_mergeRound(h64, v3); - h64 = XXH64_mergeRound(h64, v4); - - } else { - h64 = seed + PRIME64_5; - } - - h64 += (U64) len; - - return XXH64_finalize(h64, p, len, endian, align); -} - - -XXH_PUBLIC_API unsigned long long XXH64 (const void* input, size_t len, unsigned long long seed) -{ -#if 0 - /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ - XXH64_state_t state; - XXH64_reset(&state, seed); - XXH64_update(&state, input, len); - return XXH64_digest(&state); -#else - XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; - - if (XXH_FORCE_ALIGN_CHECK) { - if ((((size_t)input) & 7)==0) { /* Input is aligned, let's leverage the speed advantage */ - if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) - return XXH64_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned); - else - return XXH64_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned); - } } - - if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) - return XXH64_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned); - else - return XXH64_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned); -#endif -} - -/*====== Hash Streaming ======*/ - -XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void) -{ - return (XXH64_state_t*)XXH_malloc(sizeof(XXH64_state_t)); -} -XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr) -{ - XXH_free(statePtr); - return XXH_OK; -} - -XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dstState, const XXH64_state_t* srcState) -{ - memcpy(dstState, srcState, sizeof(*dstState)); -} - -XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH64_state_t* statePtr, unsigned long long seed) -{ - XXH64_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */ - memset(&state, 0, sizeof(state)); - state.v1 = seed + PRIME64_1 + PRIME64_2; - state.v2 = seed + PRIME64_2; - state.v3 = seed + 0; - state.v4 = seed - PRIME64_1; - /* do not write into reserved, planned to be removed in a future version */ - memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved)); - return XXH_OK; -} - -FORCE_INLINE -XXH_errorcode XXH64_update_endian (XXH64_state_t* state, const void* input, size_t len, XXH_endianess endian) -{ - const BYTE* p = (const BYTE*)input; - const BYTE* const bEnd = p + len; - - if (input==NULL) -#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) - return XXH_OK; -#else - return XXH_ERROR; -#endif - - state->total_len += len; - - if (state->memsize + len < 32) { /* fill in tmp buffer */ - XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, len); - state->memsize += (U32)len; - return XXH_OK; - } - - if (state->memsize) { /* tmp buffer is full */ - XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, 32-state->memsize); - state->v1 = XXH64_round(state->v1, XXH_readLE64(state->mem64+0, endian)); - state->v2 = XXH64_round(state->v2, XXH_readLE64(state->mem64+1, endian)); - state->v3 = XXH64_round(state->v3, XXH_readLE64(state->mem64+2, endian)); - state->v4 = XXH64_round(state->v4, XXH_readLE64(state->mem64+3, endian)); - p += 32-state->memsize; - state->memsize = 0; - } - - if (p+32 <= bEnd) { - const BYTE* const limit = bEnd - 32; - U64 v1 = state->v1; - U64 v2 = state->v2; - U64 v3 = state->v3; - U64 v4 = state->v4; - - do { - v1 = XXH64_round(v1, XXH_readLE64(p, endian)); p+=8; - v2 = XXH64_round(v2, XXH_readLE64(p, endian)); p+=8; - v3 = XXH64_round(v3, XXH_readLE64(p, endian)); p+=8; - v4 = XXH64_round(v4, XXH_readLE64(p, endian)); p+=8; - } while (p<=limit); - - state->v1 = v1; - state->v2 = v2; - state->v3 = v3; - state->v4 = v4; - } - - if (p < bEnd) { - XXH_memcpy(state->mem64, p, (size_t)(bEnd-p)); - state->memsize = (unsigned)(bEnd-p); - } - - return XXH_OK; -} - -XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* state_in, const void* input, size_t len) -{ - XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; - - if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) - return XXH64_update_endian(state_in, input, len, XXH_littleEndian); - else - return XXH64_update_endian(state_in, input, len, XXH_bigEndian); -} - -FORCE_INLINE U64 XXH64_digest_endian (const XXH64_state_t* state, XXH_endianess endian) -{ - U64 h64; - - if (state->total_len >= 32) { - U64 const v1 = state->v1; - U64 const v2 = state->v2; - U64 const v3 = state->v3; - U64 const v4 = state->v4; - - h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18); - h64 = XXH64_mergeRound(h64, v1); - h64 = XXH64_mergeRound(h64, v2); - h64 = XXH64_mergeRound(h64, v3); - h64 = XXH64_mergeRound(h64, v4); - } else { - h64 = state->v3 /*seed*/ + PRIME64_5; - } - - h64 += (U64) state->total_len; - - return XXH64_finalize(h64, state->mem64, (size_t)state->total_len, endian, XXH_aligned); -} - -XXH_PUBLIC_API unsigned long long XXH64_digest (const XXH64_state_t* state_in) -{ - XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; - - if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) - return XXH64_digest_endian(state_in, XXH_littleEndian); - else - return XXH64_digest_endian(state_in, XXH_bigEndian); -} - - -/*====== Canonical representation ======*/ - -XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash) -{ - XXH_STATIC_ASSERT(sizeof(XXH64_canonical_t) == sizeof(XXH64_hash_t)); - if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap64(hash); - memcpy(dst, &hash, sizeof(*dst)); -} - -XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src) -{ - return XXH_readBE64(src); -} - -#endif /* XXH_NO_LONG_LONG */ diff --git a/external/xxhash.h b/external/xxhash.h deleted file mode 100644 index d6bad94..0000000 --- a/external/xxhash.h +++ /dev/null @@ -1,328 +0,0 @@ -/* - xxHash - Extremely Fast Hash algorithm - Header File - Copyright (C) 2012-2016, Yann Collet. - - BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer - in the documentation and/or other materials provided with the - distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - You can contact the author at : - - xxHash source repository : https://github.com/Cyan4973/xxHash -*/ - -/* Notice extracted from xxHash homepage : - -xxHash is an extremely fast Hash algorithm, running at RAM speed limits. -It also successfully passes all tests from the SMHasher suite. - -Comparison (single thread, Windows Seven 32 bits, using SMHasher on a Core 2 Duo @3GHz) - -Name Speed Q.Score Author -xxHash 5.4 GB/s 10 -CrapWow 3.2 GB/s 2 Andrew -MumurHash 3a 2.7 GB/s 10 Austin Appleby -SpookyHash 2.0 GB/s 10 Bob Jenkins -SBox 1.4 GB/s 9 Bret Mulvey -Lookup3 1.2 GB/s 9 Bob Jenkins -SuperFastHash 1.2 GB/s 1 Paul Hsieh -CityHash64 1.05 GB/s 10 Pike & Alakuijala -FNV 0.55 GB/s 5 Fowler, Noll, Vo -CRC32 0.43 GB/s 9 -MD5-32 0.33 GB/s 10 Ronald L. Rivest -SHA1-32 0.28 GB/s 10 - -Q.Score is a measure of quality of the hash function. -It depends on successfully passing SMHasher test set. -10 is a perfect score. - -A 64-bit version, named XXH64, is available since r35. -It offers much better speed, but for 64-bit applications only. -Name Speed on 64 bits Speed on 32 bits -XXH64 13.8 GB/s 1.9 GB/s -XXH32 6.8 GB/s 6.0 GB/s -*/ - -#ifndef XXHASH_H_5627135585666179 -#define XXHASH_H_5627135585666179 1 - -#if defined (__cplusplus) -extern "C" { -#endif - - -/* **************************** -* Definitions -******************************/ -#include /* size_t */ -typedef enum { XXH_OK=0, XXH_ERROR } XXH_errorcode; - - -/* **************************** - * API modifier - ******************************/ -/** XXH_INLINE_ALL (and XXH_PRIVATE_API) - * This is useful to include xxhash functions in `static` mode - * in order to inline them, and remove their symbol from the public list. - * Inlining can offer dramatic performance improvement on small keys. - * Methodology : - * #define XXH_INLINE_ALL - * #include "xxhash.h" - * `xxhash.c` is automatically included. - * It's not useful to compile and link it as a separate module. - */ -#if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API) -# ifndef XXH_STATIC_LINKING_ONLY -# define XXH_STATIC_LINKING_ONLY -# endif -# if defined(__GNUC__) -# define XXH_PUBLIC_API static __inline __attribute__((unused)) -# elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) -# define XXH_PUBLIC_API static inline -# elif defined(_MSC_VER) -# define XXH_PUBLIC_API static __inline -# else - /* this version may generate warnings for unused static functions */ -# define XXH_PUBLIC_API static -# endif -#else -# define XXH_PUBLIC_API /* do nothing */ -#endif /* XXH_INLINE_ALL || XXH_PRIVATE_API */ - -/*! XXH_NAMESPACE, aka Namespace Emulation : - * - * If you want to include _and expose_ xxHash functions from within your own library, - * but also want to avoid symbol collisions with other libraries which may also include xxHash, - * - * you can use XXH_NAMESPACE, to automatically prefix any public symbol from xxhash library - * with the value of XXH_NAMESPACE (therefore, avoid NULL and numeric values). - * - * Note that no change is required within the calling program as long as it includes `xxhash.h` : - * regular symbol name will be automatically translated by this header. - */ -#ifdef XXH_NAMESPACE -# define XXH_CAT(A,B) A##B -# define XXH_NAME2(A,B) XXH_CAT(A,B) -# define XXH_versionNumber XXH_NAME2(XXH_NAMESPACE, XXH_versionNumber) -# define XXH32 XXH_NAME2(XXH_NAMESPACE, XXH32) -# define XXH32_createState XXH_NAME2(XXH_NAMESPACE, XXH32_createState) -# define XXH32_freeState XXH_NAME2(XXH_NAMESPACE, XXH32_freeState) -# define XXH32_reset XXH_NAME2(XXH_NAMESPACE, XXH32_reset) -# define XXH32_update XXH_NAME2(XXH_NAMESPACE, XXH32_update) -# define XXH32_digest XXH_NAME2(XXH_NAMESPACE, XXH32_digest) -# define XXH32_copyState XXH_NAME2(XXH_NAMESPACE, XXH32_copyState) -# define XXH32_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH32_canonicalFromHash) -# define XXH32_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH32_hashFromCanonical) -# define XXH64 XXH_NAME2(XXH_NAMESPACE, XXH64) -# define XXH64_createState XXH_NAME2(XXH_NAMESPACE, XXH64_createState) -# define XXH64_freeState XXH_NAME2(XXH_NAMESPACE, XXH64_freeState) -# define XXH64_reset XXH_NAME2(XXH_NAMESPACE, XXH64_reset) -# define XXH64_update XXH_NAME2(XXH_NAMESPACE, XXH64_update) -# define XXH64_digest XXH_NAME2(XXH_NAMESPACE, XXH64_digest) -# define XXH64_copyState XXH_NAME2(XXH_NAMESPACE, XXH64_copyState) -# define XXH64_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH64_canonicalFromHash) -# define XXH64_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH64_hashFromCanonical) -#endif - - -/* ************************************* -* Version -***************************************/ -#define XXH_VERSION_MAJOR 0 -#define XXH_VERSION_MINOR 6 -#define XXH_VERSION_RELEASE 5 -#define XXH_VERSION_NUMBER (XXH_VERSION_MAJOR *100*100 + XXH_VERSION_MINOR *100 + XXH_VERSION_RELEASE) -XXH_PUBLIC_API unsigned XXH_versionNumber (void); - - -/*-********************************************************************** -* 32-bit hash -************************************************************************/ -typedef unsigned int XXH32_hash_t; - -/*! XXH32() : - Calculate the 32-bit hash of sequence "length" bytes stored at memory address "input". - The memory between input & input+length must be valid (allocated and read-accessible). - "seed" can be used to alter the result predictably. - Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark) : 5.4 GB/s */ -XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t length, unsigned int seed); - -/*====== Streaming ======*/ -typedef struct XXH32_state_s XXH32_state_t; /* incomplete type */ -XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void); -XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr); -XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dst_state, const XXH32_state_t* src_state); - -XXH_PUBLIC_API XXH_errorcode XXH32_reset (XXH32_state_t* statePtr, unsigned int seed); -XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* statePtr, const void* input, size_t length); -XXH_PUBLIC_API XXH32_hash_t XXH32_digest (const XXH32_state_t* statePtr); - -/* - * Streaming functions generate the xxHash of an input provided in multiple segments. - * Note that, for small input, they are slower than single-call functions, due to state management. - * For small inputs, prefer `XXH32()` and `XXH64()`, which are better optimized. - * - * XXH state must first be allocated, using XXH*_createState() . - * - * Start a new hash by initializing state with a seed, using XXH*_reset(). - * - * Then, feed the hash state by calling XXH*_update() as many times as necessary. - * The function returns an error code, with 0 meaning OK, and any other value meaning there is an error. - * - * Finally, a hash value can be produced anytime, by using XXH*_digest(). - * This function returns the nn-bits hash as an int or long long. - * - * It's still possible to continue inserting input into the hash state after a digest, - * and generate some new hashes later on, by calling again XXH*_digest(). - * - * When done, free XXH state space if it was allocated dynamically. - */ - -/*====== Canonical representation ======*/ - -typedef struct { unsigned char digest[4]; } XXH32_canonical_t; -XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash); -XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src); - -/* Default result type for XXH functions are primitive unsigned 32 and 64 bits. - * The canonical representation uses human-readable write convention, aka big-endian (large digits first). - * These functions allow transformation of hash result into and from its canonical format. - * This way, hash values can be written into a file / memory, and remain comparable on different systems and programs. - */ - - -#ifndef XXH_NO_LONG_LONG -/*-********************************************************************** -* 64-bit hash -************************************************************************/ -typedef unsigned long long XXH64_hash_t; - -/*! XXH64() : - Calculate the 64-bit hash of sequence of length "len" stored at memory address "input". - "seed" can be used to alter the result predictably. - This function runs faster on 64-bit systems, but slower on 32-bit systems (see benchmark). -*/ -XXH_PUBLIC_API XXH64_hash_t XXH64 (const void* input, size_t length, unsigned long long seed); - -/*====== Streaming ======*/ -typedef struct XXH64_state_s XXH64_state_t; /* incomplete type */ -XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void); -XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr); -XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dst_state, const XXH64_state_t* src_state); - -XXH_PUBLIC_API XXH_errorcode XXH64_reset (XXH64_state_t* statePtr, unsigned long long seed); -XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* statePtr, const void* input, size_t length); -XXH_PUBLIC_API XXH64_hash_t XXH64_digest (const XXH64_state_t* statePtr); - -/*====== Canonical representation ======*/ -typedef struct { unsigned char digest[8]; } XXH64_canonical_t; -XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash); -XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src); -#endif /* XXH_NO_LONG_LONG */ - - - -#ifdef XXH_STATIC_LINKING_ONLY - -/* ================================================================================================ - This section contains declarations which are not guaranteed to remain stable. - They may change in future versions, becoming incompatible with a different version of the library. - These declarations should only be used with static linking. - Never use them in association with dynamic linking ! -=================================================================================================== */ - -/* These definitions are only present to allow - * static allocation of XXH state, on stack or in a struct for example. - * Never **ever** use members directly. */ - -#if !defined (__VMS) \ - && (defined (__cplusplus) \ - || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) -# include - -struct XXH32_state_s { - uint32_t total_len_32; - uint32_t large_len; - uint32_t v1; - uint32_t v2; - uint32_t v3; - uint32_t v4; - uint32_t mem32[4]; - uint32_t memsize; - uint32_t reserved; /* never read nor write, might be removed in a future version */ -}; /* typedef'd to XXH32_state_t */ - -struct XXH64_state_s { - uint64_t total_len; - uint64_t v1; - uint64_t v2; - uint64_t v3; - uint64_t v4; - uint64_t mem64[4]; - uint32_t memsize; - uint32_t reserved[2]; /* never read nor write, might be removed in a future version */ -}; /* typedef'd to XXH64_state_t */ - -# else - -struct XXH32_state_s { - unsigned total_len_32; - unsigned large_len; - unsigned v1; - unsigned v2; - unsigned v3; - unsigned v4; - unsigned mem32[4]; - unsigned memsize; - unsigned reserved; /* never read nor write, might be removed in a future version */ -}; /* typedef'd to XXH32_state_t */ - -# ifndef XXH_NO_LONG_LONG /* remove 64-bit support */ -struct XXH64_state_s { - unsigned long long total_len; - unsigned long long v1; - unsigned long long v2; - unsigned long long v3; - unsigned long long v4; - unsigned long long mem64[4]; - unsigned memsize; - unsigned reserved[2]; /* never read nor write, might be removed in a future version */ -}; /* typedef'd to XXH64_state_t */ -# endif - -# endif - - -#if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API) -# include "xxhash.c" /* include xxhash function bodies as `static`, for inlining */ -#endif - -#endif /* XXH_STATIC_LINKING_ONLY */ - - -#if defined (__cplusplus) -} -#endif - -#endif /* XXHASH_H_5627135585666179 */ diff --git a/src/blurRelaxNode.cpp b/src/blurRelaxNode.cpp index 57341df..81f96c5 100644 --- a/src/blurRelaxNode.cpp +++ b/src/blurRelaxNode.cpp @@ -59,7 +59,6 @@ SOFTWARE. #include #include -#include "external/xxhash.h" #include "blurRelax.h" #include "fastRelax.h" diff --git a/src/blurRelaxNode.h b/src/blurRelaxNode.h index eab2f5e..8df262a 100644 --- a/src/blurRelaxNode.h +++ b/src/blurRelaxNode.h @@ -59,7 +59,6 @@ SOFTWARE. #include #include -#include "external/xxhash.h" #define CHECKSTAT(m) if (!status) {status.perror(m); return status;}; From de4215ce9105b0d3fb469c09c5e6dd3e1eda5bd5 Mon Sep 17 00:00:00 2001 From: Tyler Fox Date: Wed, 11 Sep 2019 10:18:25 -0700 Subject: [PATCH 05/12] Getting things to compile in the new setup --- CMakeLists.txt | 1 + src/blurRelaxNode.cpp | 3 ++- src/blurRelaxNode.h | 8 +++----- src/fastRelax.cpp | 24 +++++++++++++----------- src/fastRelax.h | 2 +- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 68041cf..ec0a074 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 2.6) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/modules) project(BlurRelax) +set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME}) find_package(Maya REQUIRED) find_package(OpenGL REQUIRED) set(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/../output/Maya${MAYA_VERSION}) diff --git a/src/blurRelaxNode.cpp b/src/blurRelaxNode.cpp index 81f96c5..eb0d65d 100644 --- a/src/blurRelaxNode.cpp +++ b/src/blurRelaxNode.cpp @@ -40,6 +40,7 @@ SOFTWARE. #include #include #include +#include #include #include @@ -59,7 +60,7 @@ SOFTWARE. #include #include -#include "blurRelax.h" +#include "blurRelaxNode.h" #include "fastRelax.h" diff --git a/src/blurRelaxNode.h b/src/blurRelaxNode.h index 8df262a..58d4d8e 100644 --- a/src/blurRelaxNode.h +++ b/src/blurRelaxNode.h @@ -140,11 +140,9 @@ class BlurRelax : public MPxDeformerNode { // storage for this data doesn't change unless the hashes do std::vector order; std::vector invOrder; - - - std::vector> &neighbors, // A vector of neighbor indices per vertex - std::vector> &hardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary - std::vector &vertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, + std::vector> neighbors; // A vector of neighbor indices per vertex + std::vector> hardEdges; // Bitwise per-neighbor data: edge is hard, edge along boundary + std::vector vertData; // Bitwise per-vert data: Group membership, geo boundary, group boundary, std::vector shiftVal; // normally 0.5; but it's 0.25 if on a hard edge std::vector valence; // as float for vectorizing std::vector creaseCount; diff --git a/src/fastRelax.cpp b/src/fastRelax.cpp index 8c04141..434b544 100644 --- a/src/fastRelax.cpp +++ b/src/fastRelax.cpp @@ -27,6 +27,7 @@ SOFTWARE. #include #include #include "fastRelax.h" +#include "blurRelaxNode.h" /* This code takes the edges that have been defined as hard @@ -35,6 +36,8 @@ SOFTWARE. Perhaps in the future, I can project onto non-branching strings of edges. But for now, it's just direct neighbors */ + + void edgeProject( const float_t basePoints[][4], const std::vector &group, @@ -181,7 +184,7 @@ void fillQuickTopoVars( // Inputs std::vector> rawNeighbors, // A vector of neighbor indices per vertex. Copied std::vector> rawHardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary. Copied - const std::vector &rawVertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, + const std::vector &rawVertData, // Bitwise per-vert data: Group membership, geo boundary, group boundary, // Outputs std::vector> &neighbors, @@ -201,7 +204,7 @@ void fillQuickTopoVars( for (size_t start=0; start> rawNeighbors, // A vector of neighbor indices per vertex. Copied std::vector> rawHardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary. Copied - const std::vector &rawVertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, + const std::vector &rawVertData, // Bitwise per-vert data: Group membership, geo boundary, group boundary, // Outputs std::vector> &neighbors, From 9abae7e6f26e7c0c2bc066272de4729f8e4883f5 Mon Sep 17 00:00:00 2001 From: Tyler Fox Date: Wed, 11 Sep 2019 11:05:35 -0700 Subject: [PATCH 06/12] Get it compiling --- src/blurRelaxNode.cpp | 139 +++++++++++++++++++++++++++++------------ src/blurRelaxNode.h | 107 +++++-------------------------- src/fastRelax.cpp | 125 ++++++++---------------------------- src/fastRelax.h | 48 ++++++++------ src/pluginRegister.cpp | 2 +- 5 files changed, 172 insertions(+), 249 deletions(-) diff --git a/src/blurRelaxNode.cpp b/src/blurRelaxNode.cpp index eb0d65d..b6083b8 100644 --- a/src/blurRelaxNode.cpp +++ b/src/blurRelaxNode.cpp @@ -23,42 +23,27 @@ SOFTWARE. */ #include -#include #include #include #include #include -#include #include -#include -#include +#include #include #include -#include #include #include #include +#include +#include #include #include +#include #include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include #include "blurRelaxNode.h" #include "fastRelax.h" @@ -311,7 +296,7 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM // Populate the variables with *SPECIALLY ORDERED* data // all vertex data is now shuffled by the order vector - std::vector> rawNeighbors; // A vector of neighbor indices per vertex + std::vector> rawNeighbors; // A vector of neighbor indices per vertex std::vector> rawHardEdges; // Bitwise per-neighbor data: edge is hard, edge along boundary std::vector rawVertData; // Bitwise per-vert data: Group membership, geo boundary, group boundary, @@ -330,18 +315,18 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM // Build the raw float data buffers pointArray_t mpa; - float_t(*reoVerts)[4] = new float_t[numVerts][4]; + FLOAT(*reoVerts)[4] = new FLOAT[tNumVerts][4]; meshFn.getPoints(mpa); - for (size_t i = 0; i < numVerts; ++i) { - reoVerts[i][0] = mpa[order[i]].x; - reoVerts[i][1] = mpa[order[i]].y; - reoVerts[i][2] = mpa[order[i]].z; - reoVerts[i][3] = mpa[order[i]].w; + for (size_t i = 0; i < tNumVerts; ++i) { + reoVerts[i][0] = mpa[(UINT)order[i]].x; + reoVerts[i][1] = mpa[(UINT)order[i]].y; + reoVerts[i][2] = mpa[(UINT)order[i]].z; + reoVerts[i][3] = mpa[(UINT)order[i]].w; } // Calculate the relax, and store in verts - quickRelax(mesh, bb, hb, gb, reproject, tBias, iterations, numVerts, group, reoVerts); + quickRelax(mesh, bb, hb, gb, reproject, tBias, iterations, tNumVerts, vertData, reoVerts); // Get the painted weight values std::vector weightVals; @@ -366,28 +351,28 @@ void BlurRelax::quickRelax( const short groupEdgeBehavior, const bool reproject, const float taubinBias, - const float_t iterations, + const FLOAT iterations, const UINT numVerts, const std::vector &group, - float_t(*verts)[4] + FLOAT(*verts)[4] ) { bool rpEdges = (borderBehavior == BB_SLIDE) || (hardEdgeBehavior == HB_SLIDE) || (groupEdgeBehavior == GB_SLIDE); std::vector groupIdxs; - float_t (*baseVerts)[4]; + FLOAT (*baseVerts)[4]; if (rpEdges) { for (size_t i = 0; i < group.size(); ++i) { if (group[i]) groupIdxs.push_back(i); } // make a copy of the original verts only if they'll be used for edge reprojection - baseVerts = new float_t[numVerts][4]; - memcpy(&(baseVerts[0][0]), &(verts[0][0]), 4 * numVerts * sizeof(float_t)); + baseVerts = new FLOAT[numVerts][4]; + memcpy(&(baseVerts[0][0]), &(verts[0][0]), 4 * numVerts * sizeof(FLOAT)); } - float_t(*prevVerts)[4]; - prevVerts = new float_t[numVerts][4]; + FLOAT(*prevVerts)[4]; + prevVerts = new FLOAT[numVerts][4]; - float_t iterT, iterFI; + FLOAT iterT, iterFI; iterT = modf(iterations, &iterFI); UINT iterI = (UINT)iterFI; if (iterT > 0.0) { @@ -414,7 +399,7 @@ void BlurRelax::quickRelax( for (size_t r = 0; r < iterI; ++r) { if ((r == iterI - 1) && (iterT > 0.0)){ // Store the next-to-last iteration to interpolate with - memcpy(&(prevVerts[0][0]), &(verts[0][0]), 4 * numVerts * sizeof(float_t)); + memcpy(&(prevVerts[0][0]), &(verts[0][0]), 4 * numVerts * sizeof(FLOAT)); } quickLaplacianSmooth(verts, numVerts, neighbors, valence, shiftVal); if (taubinBias < 1.0){ @@ -422,7 +407,7 @@ void BlurRelax::quickRelax( } if (rpEdges) { - edgeProject(baseVerts, groupIdxs, invOrder, neighbors, hardEdges, creaseCount, verts); + edgeProject(baseVerts, groupIdxs, invOrder, neighbors, creaseCount, verts); } if (reproject) { @@ -445,8 +430,8 @@ void BlurRelax::quickRelax( // Interpolate between prevVerts and verts based on iterT if (iterT > 0.0) { // This should vectorize - float_t * vv = &verts[0][0]; - float_t * pv = &prevVerts[0][0]; + FLOAT * vv = &verts[0][0]; + FLOAT * pv = &prevVerts[0][0]; for (size_t i = 0; i < numVerts * 4; ++i) { vv[i] = ((vv[i] - pv[i]) * iterT) + pv[i]; } @@ -456,3 +441,79 @@ void BlurRelax::quickRelax( delete[] prevVerts; } + + + +/* + Load the minimal topology data from Maya +*/ +void loadMayaTopologyData( + // Inputs + MObject &mesh, + MItGeometry& vertIter, + + //outputs + std::vector> &neighbors, // A vector of neighbor indices per vertex + std::vector> &hardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary + std::vector &vertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, +){ + MFnMesh meshFn(mesh); + UINT numVertices = meshFn.numVertices(); + vertData.resize(numVertices); + hardEdges.resize(numVertices); + neighbors.resize(numVertices); + + for (; !vertIter.isDone(); vertIter.next()) { + vertData[vertIter.index()] = V_IN_GROUP; + } + vertIter.reset(); + + MItMeshEdge edgeIter(mesh); + for (; !edgeIter.isDone(); edgeIter.next()) { + const UINT start = edgeIter.index(0); + const UINT end = edgeIter.index(1); + + char edgeData; + if (edgeIter.onBoundary()) { + edgeData |= E_MESH_BORDER; + vertData[start] |= V_MESH_BORDER; + vertData[end] |= V_MESH_BORDER; + } + if (!edgeIter.isSmooth()) edgeData |= E_HARD; + + neighbors[start].push_back(end); + neighbors[end].push_back(start); + + + if (vertData[start] & V_IN_GROUP) { + if (!(vertData[end] & V_IN_GROUP)) vertData[start] |= V_GROUP_BORDER; + } + else if (vertData[end] & V_IN_GROUP) vertData[end] |= V_GROUP_BORDER; + + // an edge is a group border edge iff + // one of the two faces bordering this edge + // has a vertex that is not in the group + bool internalEdge = true; + MIntArray connFaces; + edgeIter.getConnectedFaces(connFaces); + for (UINT i=0; i -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include +#pragma once #include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include #include -#include -#include -#include - - -#define CHECKSTAT(m) if (!status) {status.perror(m); return status;}; - -#define BB_NONE 0 -#define BB_PIN 1 -#define BB_SLIDE 2 - -#define HB_NONE 0 -#define HB_PIN 1 -#define HB_SLIDE 2 - -#define GB_NONE 0 -#define GB_PIN 1 -#define GB_SLIDE 2 - -#define SA_LAPLACIAN 0 -#define SA_TAUBIN 1 +#include "fastRelax.h" #define DEFORMER_NAME "BlurRelax" +#define CHECKSTAT(m) if (!status) {status.perror(m); return status;}; // Double vs Float -#define float_t double -#define point_t MPoint -#define pointArray_t MPointArray - - -void edgeProject( - const float_t basePoints[][4], - const std::vector &group, - const std::vector &invOrder, - const std::vector> &neighbors, - const std::vector> &hardEdges, - const std::vector &creaseCount, - float_t smoothPoints[][4] -); - -void quickLaplacianSmooth( - float_t verts2d[][4], - const size_t numVerts, - const std::vector> &neighbors, - const std::vector &valence, - const std::vector &shiftVal, - const std::vector &pinPoints, - const float_t taubinBias=1.0 +typedef MPoint point_t; +typedef MPointArray pointArray_t; + +void loadMayaTopologyData( + MObject &mesh, + MItGeometry& vertIter, + std::vector> &neighbors, // A vector of neighbor indices per vertex + std::vector> &hardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary + std::vector &vertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, ); class BlurRelax : public MPxDeformerNode { @@ -140,11 +77,11 @@ class BlurRelax : public MPxDeformerNode { // storage for this data doesn't change unless the hashes do std::vector order; std::vector invOrder; - std::vector> neighbors; // A vector of neighbor indices per vertex + std::vector> neighbors; // A vector of neighbor indices per vertex std::vector> hardEdges; // Bitwise per-neighbor data: edge is hard, edge along boundary std::vector vertData; // Bitwise per-vert data: Group membership, geo boundary, group boundary, - std::vector shiftVal; // normally 0.5; but it's 0.25 if on a hard edge - std::vector valence; // as float for vectorizing + std::vector shiftVal; // normally 0.5; but it's 0.25 if on a hard edge + std::vector valence; // as float for vectorizing std::vector creaseCount; MStatus getTrueWeights( @@ -155,16 +92,6 @@ class BlurRelax : public MPxDeformerNode { float envelope ) const; - void BlurRelax::buildQuickData( - MObject &mesh, - MItGeometry& vertIter, - short borderBehavior, - short hardEdgeBehavior, - short groupEdgeBehavior, - bool reproject, - std::vector &group - ); - void quickRelax( MObject &mesh, const short borderBehavior, @@ -172,10 +99,10 @@ class BlurRelax : public MPxDeformerNode { const short groupEdgeBehavior, const bool reproject, const float taubinBias, - const float_t iterations, + const FLOAT iterations, const UINT numVerts, const std::vector &group, - float_t(*verts)[4] // already resized + FLOAT(*verts)[4] // already resized ); }; diff --git a/src/fastRelax.cpp b/src/fastRelax.cpp index 434b544..a52e2cf 100644 --- a/src/fastRelax.cpp +++ b/src/fastRelax.cpp @@ -25,9 +25,7 @@ SOFTWARE. #include #include #include -#include #include "fastRelax.h" -#include "blurRelaxNode.h" /* This code takes the edges that have been defined as hard @@ -39,12 +37,12 @@ SOFTWARE. void edgeProject( - const float_t basePoints[][4], + const FLOAT basePoints[][NUM_COMPS], const std::vector &group, const std::vector &invOrder, const std::vector> &neighbors, const std::vector &creaseCount, - float_t smoothPoints[][4] + FLOAT smoothPoints[][NUM_COMPS] ) { std::vector neigh; for (size_t gidx = 0; gidx < group.size(); ++gidx) { @@ -54,10 +52,10 @@ void edgeProject( // assume that all stored neighbors are hard. size_t idx = invOrder[group[gidx]]; - float_t *avg = smoothPoints[idx]; - const float_t *basePos = basePoints[idx]; + FLOAT *avg = smoothPoints[idx]; + const FLOAT *basePos = basePoints[idx]; if (creaseCount[idx] != 2) { continue; } - float_t keep[3], delta[3], edge[3]; + FLOAT keep[3], delta[3], edge[3]; neigh.clear(); for (size_t i = 0; i < neighbors.size(); ++i) { @@ -68,7 +66,7 @@ void edgeProject( } // TODO: Find the hard edge strings and reproject onto those - float_t minLen = std::numeric_limits::max(); + FLOAT minLen = std::numeric_limits::max(); bool found = false; // avg - basePos @@ -81,26 +79,26 @@ void edgeProject( // normalized(prevPoints[n] - basePos) for (size_t x = 0; x < 3; ++x) edge[x] = basePoints[n][x] - basePos[x]; - float_t elen = sqrt(edge[0] * edge[0] + edge[1] * edge[1] + edge[2] * edge[2]); + FLOAT elen = sqrt(edge[0] * edge[0] + edge[1] * edge[1] + edge[2] * edge[2]); for (size_t x = 0; x < 3; ++x) edge[x] /= elen; // dot(delta, edge) - float_t dd = delta[0] * edge[0] + delta[1] * edge[1] + delta[2] * edge[2]; + FLOAT dd = delta[0] * edge[0] + delta[1] * edge[1] + delta[2] * edge[2]; - float_t dn[3] = { 0.0f, 0.0f, 0.0f }; + FLOAT dn[3] = { 0.0f, 0.0f, 0.0f }; if (dd > 0.0) { for (size_t x = 0; x < 3; ++x) dn[x] = edge[x] * dd; } - float_t xx[3]; + FLOAT xx[3]; // delta - dn for (size_t x = 0; x < 3; ++x) xx[x] = delta[x] - dn[x]; // dot(xx, xx) - float_t len2 = xx[0] * xx[0] + xx[1] * xx[1] + xx[2] * xx[2]; + FLOAT len2 = xx[0] * xx[0] + xx[1] * xx[1] + xx[2] * xx[2]; if (len2 < minLen) { minLen = len2; @@ -128,16 +126,17 @@ void edgeProject( with the most neighbors were at the top. neighborOffsets contains the index offsets of vertices with at least [index] of neighbors */ + void quickLaplacianSmooth( - float_t verts2d[][4], + FLOAT verts2d[][NUM_COMPS], const size_t numVerts, const std::vector> &neighbors, - const std::vector &valence, - const std::vector &shiftVal, - const float_t taubinBias=1.0 + const std::vector &valence, + const std::vector &shiftVal, + const FLOAT taubinBias ) { // First, get verts as a single pointer to the contiguous memory stored in (verts2d*)[4] - float_t* verts = &(verts2d[0][0]); + FLOAT* verts = &(verts2d[0][0]); // number of nonzero valence size_t nzv = neighbors[0].size(); @@ -148,8 +147,8 @@ void quickLaplacianSmooth( // The __restrict keyword tells the compiler that *outComp // is not pointed to by any other pointer in this scope // This allows for auto-vectorization - float_t * __restrict outComp = new float_t[nzc]; - memset(outComp, 0, nzc*sizeof(float_t)); + FLOAT * __restrict outComp = new FLOAT[nzc]; + memset(outComp, 0, nzc*sizeof(FLOAT)); for (size_t ncIdx = 0; ncIdx < neighbors.size(); ++ncIdx) { const auto &nCol = neighbors[ncIdx]; @@ -166,7 +165,7 @@ void quickLaplacianSmooth( outComp[i] = shiftVal[i] * taubinBias * ((outComp[i] / valence[i]) - verts[i]) + verts[i]; } - memcpy(verts, outComp, nzc*sizeof(float_t)); + memcpy(verts, outComp, nzc*sizeof(FLOAT)); delete outComp; } @@ -182,15 +181,15 @@ void fillQuickTopoVars( short groupEdgeBehavior, // GB_NONE/GB_PIN/GB_SLIDE // Inputs - std::vector> rawNeighbors, // A vector of neighbor indices per vertex. Copied + std::vector> rawNeighbors, // A vector of neighbor indices per vertex. Copied std::vector> rawHardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary. Copied const std::vector &rawVertData, // Bitwise per-vert data: Group membership, geo boundary, group boundary, // Outputs std::vector> &neighbors, std::vector &creaseCount, - std::vector &shiftVal, - std::vector &valence, + std::vector &shiftVal, + std::vector &valence, std::vector &order, std::vector &invOrder ){ @@ -228,7 +227,7 @@ void fillQuickTopoVars( } } - std::vector rawShiftVal; + std::vector rawShiftVal; rawShiftVal.resize(numVertices); std::fill(rawShiftVal.begin(), rawShiftVal.end(), 0.5); @@ -241,7 +240,7 @@ void fillQuickTopoVars( rawShiftVal[i] = 0.25; if (rawCreaseCount[i] != 2) pinThisPoint = true; if (!pinThisPoint) { - std::vector newNeigh; + std::vector newNeigh; std::vector newHard; for (size_t j = 0; j < rawNeighbors[i].size(); ++j) { if (rawHardEdges[i][j] & E_HARD) { @@ -285,82 +284,10 @@ void fillQuickTopoVars( // Vectorizing flattens the vert list, so I need this data per vert, per component // Maya uses xyzw points, so I need 4. In other cases I'll need 3 for (size_t xx = 0; xx < NUM_COMPS; ++xx) { - valence[NUM_COMPS * i + xx] = float_t(vale); + valence[NUM_COMPS * i + xx] = FLOAT(vale); shiftVal[NUM_COMPS * i + xx] = rawShiftVal[order[i]]; } } } -/* - Load the minimal topology data from Maya -*/ -void loadMayaTopologyData( - // Inputs - MObject &mesh, - MItGeometry& vertIter, - - //outputs - std::vector> &neighbors, // A vector of neighbor indices per vertex - std::vector> &hardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary - std::vector &vertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, -){ - MFnMesh meshFn(mesh); - UINT numVertices = meshFn.numVertices(); - vertData.resize(numVertices); - hardEdges.resize(numVertices); - neighbors.resize(numVertices); - - for (; !vertIter.isDone(); vertIter.next()) { - vertData[vertIter.index()] = V_IN_GROUP; - } - vertIter.reset(); - - MItMeshEdge edgeIter(mesh); - for (; !edgeIter.isDone(); edgeIter.next()) { - const UINT start = edgeIter.index(0); - const UINT end = edgeIter.index(1); - - char edgeData; - if (edgeIter.onBoundary()) { - edgeData |= E_MESH_BORDER; - vertData[start] |= V_MESH_BORDER; - vertData[end] |= V_MESH_BORDER; - } - if (!edgeIter.isSmooth()) edgeData |= E_HARD; - - neighbors[start].push_back(end); - neighbors[end].push_back(start); - - - if (vertData[start] & V_IN_GROUP) { - if (!(vertData[end] & V_IN_GROUP)) vertData[start] |= V_GROUP_BORDER; - } - else if (vertData[end] & V_IN_GROUP) vertData[end] |= V_GROUP_BORDER; - - // an edge is a group border edge iff - // one of the two faces bordering this edge - // has a vertex that is not in the group - bool internalEdge = true; - MIntArray connFaces; - edgeIter.getConnectedFaces(connFaces); - for (UINT i=0; i -#include -#include +typedef double FLOAT; +typedef unsigned int UINT; +#define NUM_COMPS 4 + +// Yes, these should be enums. I'll get to it #define V_IN_GROUP 1 #define V_MESH_BORDER 2 #define V_GROUP_BORDER 4 @@ -34,33 +37,38 @@ SOFTWARE. #define E_MESH_BORDER 2 #define E_GROUP_BORDER 4 -#define NUM_COMPS 4 +#define BB_NONE 0 +#define BB_PIN 1 +#define BB_SLIDE 2 + +#define HB_NONE 0 +#define HB_PIN 1 +#define HB_SLIDE 2 + +#define GB_NONE 0 +#define GB_PIN 1 +#define GB_SLIDE 2 + +#define SA_LAPLACIAN 0 +#define SA_TAUBIN 1 void edgeProject( - const float_t basePoints[][NUM_COMPS], + const FLOAT basePoints[][NUM_COMPS], const std::vector &group, const std::vector &invOrder, const std::vector> &neighbors, const std::vector &creaseCount, - float_t smoothPoints[][NUM_COMPS] + FLOAT smoothPoints[][NUM_COMPS] ); void quickLaplacianSmooth( - float_t verts2d[][NUM_COMPS], + FLOAT verts2d[][NUM_COMPS], const size_t numVerts, const std::vector> &neighbors, - const std::vector &valence, - const std::vector &shiftVal, - const float_t taubinBias=1.0 -); - -void loadMayaTopologyData( - MObject &mesh, - MItGeometry& vertIter, - std::vector> &neighbors, // A vector of neighbor indices per vertex - std::vector> &hardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary - std::vector &vertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, + const std::vector &valence, + const std::vector &shiftVal, + const FLOAT taubinBias=1.0 ); void fillQuickTopoVars( @@ -70,15 +78,15 @@ void fillQuickTopoVars( short groupEdgeBehavior, // GB_NONE/GB_PIN/GB_SLIDE // Inputs - std::vector> rawNeighbors, // A vector of neighbor indices per vertex. Copied + std::vector> rawNeighbors, // A vector of neighbor indices per vertex. Copied std::vector> rawHardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary. Copied const std::vector &rawVertData, // Bitwise per-vert data: Group membership, geo boundary, group boundary, // Outputs std::vector> &neighbors, std::vector &creaseCount, - std::vector &shiftVal, - std::vector &valence, + std::vector &shiftVal, + std::vector &valence, std::vector &order, std::vector &invOrder ); diff --git a/src/pluginRegister.cpp b/src/pluginRegister.cpp index 27b84e6..f5b1edc 100644 --- a/src/pluginRegister.cpp +++ b/src/pluginRegister.cpp @@ -23,7 +23,7 @@ SOFTWARE. */ #include -#include "blurRelax.h" +#include "blurRelaxNode.h" MStatus initializePlugin(MObject obj) { MStatus result; From 0af268d479e66c473d530cbe2b7841feebc13499 Mon Sep 17 00:00:00 2001 From: Tyler Fox Date: Wed, 11 Sep 2019 18:31:06 -0700 Subject: [PATCH 07/12] Pull out the maya-specific interface into its own class --- src/blurRelaxNode.cpp | 260 ++++++++++-------------------------------- src/blurRelaxNode.h | 45 +------- src/fastMayaRelax.cpp | 171 +++++++++++++++++++++++++++ src/fastMayaRelax.h | 31 +++++ src/fastRelax.cpp | 106 ++++++++--------- src/fastRelax.h | 109 +++++++----------- 6 files changed, 361 insertions(+), 361 deletions(-) create mode 100644 src/fastMayaRelax.cpp create mode 100644 src/fastMayaRelax.h diff --git a/src/blurRelaxNode.cpp b/src/blurRelaxNode.cpp index b6083b8..c640187 100644 --- a/src/blurRelaxNode.cpp +++ b/src/blurRelaxNode.cpp @@ -125,45 +125,45 @@ MStatus BlurRelax::initialize() { MFnNumericAttribute nAttr; MFnTypedAttribute tAttr; - aBorderBehavior = eAttr.create("borderBehavior", "bb", BB_SLIDE, &status); + aBorderBehavior = eAttr.create("borderBehavior", "bb", (UCHAR)B::SLIDE, &status); CHECKSTAT("aBorderBehavior"); eAttr.setKeyable(false); eAttr.setChannelBox(true); - status = eAttr.addField("None", BB_NONE); + status = eAttr.addField("None", (UCHAR)B::NONE); CHECKSTAT("aBorderBehavior"); - status = eAttr.addField("Pin", BB_PIN); + status = eAttr.addField("Pin", (UCHAR)B::PIN); CHECKSTAT("aBorderBehavior"); - status = eAttr.addField("Slide", BB_SLIDE); + status = eAttr.addField("Slide", (UCHAR)B::SLIDE); CHECKSTAT("aBorderBehavior"); status = addAttribute(aBorderBehavior); CHECKSTAT("aBorderBehavior"); status = attributeAffects(aBorderBehavior, outputGeom); CHECKSTAT("aBorderBehavior"); - aHardEdgeBehavior = eAttr.create("hardEdgeBehavior", "hb", HB_SLIDE, &status); + aHardEdgeBehavior = eAttr.create("hardEdgeBehavior", "hb", (UCHAR)B::SLIDE, &status); CHECKSTAT("aHardEdgeBehavior"); eAttr.setKeyable(false); eAttr.setChannelBox(true); - status = eAttr.addField("None", HB_NONE); + status = eAttr.addField("None", (UCHAR)B::NONE); CHECKSTAT("aHardEdgeBehavior"); - status = eAttr.addField("Pin", HB_PIN); + status = eAttr.addField("Pin", (UCHAR)B::PIN); CHECKSTAT("aHardEdgeBehavior"); - status = eAttr.addField("Slide", HB_SLIDE); + status = eAttr.addField("Slide", (UCHAR)B::SLIDE); CHECKSTAT("aHardEdgeBehavior"); status = addAttribute(aHardEdgeBehavior); CHECKSTAT("aHardEdgeBehavior"); status = attributeAffects(aHardEdgeBehavior, outputGeom); CHECKSTAT("aHardEdgeBehavior"); - aGroupEdgeBehavior = eAttr.create("groupEdgeBehavior", "gb", GB_PIN, &status); + aGroupEdgeBehavior = eAttr.create("groupEdgeBehavior", "gb", (UCHAR)B::PIN, &status); CHECKSTAT("aGroupEdgeBehavior"); eAttr.setKeyable(false); eAttr.setChannelBox(true); - status = eAttr.addField("None", GB_NONE); + status = eAttr.addField("None", (UCHAR)B::NONE); CHECKSTAT("aGroupEdgeBehavior"); - status = eAttr.addField("Pin", GB_PIN); + status = eAttr.addField("Pin", (UCHAR)B::PIN); CHECKSTAT("aGroupEdgeBehavior"); - status = eAttr.addField("Slide", GB_SLIDE); + status = eAttr.addField("Slide", (UCHAR)B::SLIDE); CHECKSTAT("aGroupEdgeBehavior"); status = addAttribute(aGroupEdgeBehavior); CHECKSTAT("aGroupEdgeBehavior"); @@ -201,6 +201,8 @@ MStatus BlurRelax::initialize() { aRecomputeTopo = nAttr.create("recompute", "r", MFnNumericData::kBoolean, false, &status); CHECKSTAT("aRecomputeTopo"); + nAttr.setKeyable(false); + nAttr.setChannelBox(true); status = addAttribute(aRecomputeTopo); CHECKSTAT("aRecomputeTopo"); status = attributeAffects(aRecomputeTopo, outputGeom); @@ -208,6 +210,8 @@ MStatus BlurRelax::initialize() { aDeltaMush = nAttr.create("delta", "d", MFnNumericData::kBoolean, false, &status); CHECKSTAT("aDeltaMush"); + nAttr.setKeyable(false); + nAttr.setChannelBox(true); status = addAttribute(aDeltaMush); CHECKSTAT("aDeltaMush"); status = attributeAffects(aDeltaMush, outputGeom); @@ -259,6 +263,12 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM MDataHandle hTBias = dataBlock.inputValue(aTaubinBias); float tBias = hTBias.asFloat(); + + MDataHandle hDoDelta = dataBlock.inputValue(aDeltaMush); + bool doDelta = hDoDelta.asBool(); + MDataHandle hDeltaMult = dataBlock.inputValue(aDeltaMult); + float deltaMult = hDeltaMult.asFloat(); + // volume preservation uses 2 steps per iteration // so half the number of iterations if I'm volume preserving // The iterations interpolating as floats takes care of 99% of the jumping @@ -275,17 +285,16 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM tBias = -2.05f * tBias + 1.0f; // get the input mesh corresponding to this output - MObject thisNode = this->thisMObject(); - MPlug inPlug(thisNode, input); + MPlug inPlug(thisMObject(), input); inPlug.selectAncestorLogicalIndex(multiIndex, input); MDataHandle hInput = dataBlock.inputValue(inPlug); MObject mesh = hInput.asMesh(); // Get the point values MFnMesh meshFn(mesh); - int tNumVerts = meshFn.numVertices(); - int tNumPolys = meshFn.numPolygons(); - int tNumEdges = meshFn.numEdges(); + UINT tNumVerts = meshFn.numVertices(); + UINT tNumPolys = meshFn.numPolygons(); + UINT tNumEdges = meshFn.numEdges(); if (recompute || (bbCheck != bb) || (hbCheck != hb) || (gbCheck != gb) || @@ -297,22 +306,46 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM // all vertex data is now shuffled by the order vector std::vector> rawNeighbors; // A vector of neighbor indices per vertex - std::vector> rawHardEdges; // Bitwise per-neighbor data: edge is hard, edge along boundary - std::vector rawVertData; // Bitwise per-vert data: Group membership, geo boundary, group boundary, - - loadMayaTopologyData(mesh, vertIter, rawNeighbors, rawHardEdges, rawVertData); - fillQuickTopoVars( - bb, hb, gb, rawNeighbors, rawHardEdges, rawVertData, - neighbors, creaseCount, shiftVal, valence, order, invOrder - ); + std::vector> rawHardEdges; // Bitwise per-neighbor data: edge is hard, edge along boundary + std::vector vertData; + loadMayaTopologyData(mesh, meshFn, vertIter, rawNeighbors, rawHardEdges, vertData); + + if (relaxer != NULL) delete relaxer; + relaxer = new Relaxer(bb, hb, gb, rawNeighbors, rawHardEdges, vertData); } // This can happen if the user is pinning all the points // or all the edges are hard (like when you import an obj) - if (neighbors.empty()) { - return status; + if (relaxer == NULL) return status; + if (relaxer->neighbors.empty()) return status; + + + if (doDelta) { + // get the input mesh corresponding to this output + MPlug pDeltaBase(thisMObject(), aDeltaBase); + status = pDeltaBase.selectAncestorLogicalIndex(multiIndex, aDeltaBase); + CHECKSTAT("deltaBase Select") + MDataHandle hDeltaBase = dataBlock.inputValue(pDeltaBase, &status); + if (!status) { + MGlobal::displayError("Invalid delta base mesh"); + return status; + } + MObject deltaBase = hDeltaBase.asMesh(); + if (!deltaBase.isNull()) { + MGlobal::displayError("Null delta base mesh"); + return MStatus::kFailure; + } + MFnMesh deltaBaseFn(deltaBase); + // Just pass the point positions from deltaBaseFn to this->relaxer + } + + + + + + // Build the raw float data buffers pointArray_t mpa; FLOAT(*reoVerts)[4] = new FLOAT[tNumVerts][4]; @@ -326,7 +359,9 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM } // Calculate the relax, and store in verts - quickRelax(mesh, bb, hb, gb, reproject, tBias, iterations, tNumVerts, vertData, reoVerts); + + bool slide = (borderBehavior == (UCHAR)B::SLIDE) || (hardEdgeBehavior == (UCHAR)B::SLIDE) || (groupEdgeBehavior == (UCHAR)B::SLIDE); + quickRelax(mesh, slide, reproject, tBias, iterations, tNumVerts, vertData, reoVerts); // Get the painted weight values std::vector weightVals; @@ -344,176 +379,7 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM return status; } -void BlurRelax::quickRelax( - MObject &mesh, - const short borderBehavior, - const short hardEdgeBehavior, - const short groupEdgeBehavior, - const bool reproject, - const float taubinBias, - const FLOAT iterations, - const UINT numVerts, - const std::vector &group, - FLOAT(*verts)[4] -) { - bool rpEdges = (borderBehavior == BB_SLIDE) || (hardEdgeBehavior == HB_SLIDE) || (groupEdgeBehavior == GB_SLIDE); - std::vector groupIdxs; - - FLOAT (*baseVerts)[4]; - if (rpEdges) { - for (size_t i = 0; i < group.size(); ++i) { - if (group[i]) groupIdxs.push_back(i); - } - // make a copy of the original verts only if they'll be used for edge reprojection - baseVerts = new FLOAT[numVerts][4]; - memcpy(&(baseVerts[0][0]), &(verts[0][0]), 4 * numVerts * sizeof(FLOAT)); - } - - FLOAT(*prevVerts)[4]; - prevVerts = new FLOAT[numVerts][4]; - - FLOAT iterT, iterFI; - iterT = modf(iterations, &iterFI); - UINT iterI = (UINT)iterFI; - if (iterT > 0.0) { - iterI += 1; - } - - size_t nonzeroValence = neighbors[0].size(); - - MStatus status; - MMeshIntersector octree; - MObject smoothMeshPar, smoothMesh; - MFnMesh meshFn(mesh); - if (reproject) { - MFnMeshData smoothMeshParFn; - MMeshSmoothOptions smoothOpt; - smoothMeshPar = smoothMeshParFn.create(); - smoothOpt.setDivisions(1); - smoothOpt.setKeepBorderEdge(rpEdges); - smoothOpt.setSubdivisionType(MMeshSmoothOptions::kCatmullClark); - smoothMesh = meshFn.generateSmoothMesh(smoothMeshPar, &smoothOpt); - octree.create(smoothMesh); - } - - for (size_t r = 0; r < iterI; ++r) { - if ((r == iterI - 1) && (iterT > 0.0)){ - // Store the next-to-last iteration to interpolate with - memcpy(&(prevVerts[0][0]), &(verts[0][0]), 4 * numVerts * sizeof(FLOAT)); - } - quickLaplacianSmooth(verts, numVerts, neighbors, valence, shiftVal); - if (taubinBias < 1.0){ - quickLaplacianSmooth(verts, numVerts, neighbors, valence, shiftVal, taubinBias); - } - - if (rpEdges) { - edgeProject(baseVerts, groupIdxs, invOrder, neighbors, creaseCount, verts); - } - - if (reproject) { - #pragma omp parallel for if(numVerts>2000) - for (int i = 0; i < nonzeroValence; ++i) { - if ((creaseCount[i] == 0) && (group[order[i]])) { - point_t mf(verts[i][0], verts[i][1], verts[i][2]); - MPointOnMesh pom; - octree.getClosestPoint(mf, pom); - point_t gpf = pom.getPoint(); - //mfp.set(gpf, i); - verts[i][0] = gpf[0]; - verts[i][1] = gpf[1]; - verts[i][2] = gpf[2]; - } - } - } - } - - // Interpolate between prevVerts and verts based on iterT - if (iterT > 0.0) { - // This should vectorize - FLOAT * vv = &verts[0][0]; - FLOAT * pv = &prevVerts[0][0]; - for (size_t i = 0; i < numVerts * 4; ++i) { - vv[i] = ((vv[i] - pv[i]) * iterT) + pv[i]; - } - } - - if (rpEdges) delete[] baseVerts; - delete[] prevVerts; -} - - - - /* Load the minimal topology data from Maya */ -void loadMayaTopologyData( - // Inputs - MObject &mesh, - MItGeometry& vertIter, - - //outputs - std::vector> &neighbors, // A vector of neighbor indices per vertex - std::vector> &hardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary - std::vector &vertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, -){ - MFnMesh meshFn(mesh); - UINT numVertices = meshFn.numVertices(); - vertData.resize(numVertices); - hardEdges.resize(numVertices); - neighbors.resize(numVertices); - - for (; !vertIter.isDone(); vertIter.next()) { - vertData[vertIter.index()] = V_IN_GROUP; - } - vertIter.reset(); - - MItMeshEdge edgeIter(mesh); - for (; !edgeIter.isDone(); edgeIter.next()) { - const UINT start = edgeIter.index(0); - const UINT end = edgeIter.index(1); - - char edgeData; - if (edgeIter.onBoundary()) { - edgeData |= E_MESH_BORDER; - vertData[start] |= V_MESH_BORDER; - vertData[end] |= V_MESH_BORDER; - } - if (!edgeIter.isSmooth()) edgeData |= E_HARD; - - neighbors[start].push_back(end); - neighbors[end].push_back(start); - - - if (vertData[start] & V_IN_GROUP) { - if (!(vertData[end] & V_IN_GROUP)) vertData[start] |= V_GROUP_BORDER; - } - else if (vertData[end] & V_IN_GROUP) vertData[end] |= V_GROUP_BORDER; - - // an edge is a group border edge iff - // one of the two faces bordering this edge - // has a vertex that is not in the group - bool internalEdge = true; - MIntArray connFaces; - edgeIter.getConnectedFaces(connFaces); - for (UINT i=0; i> &neighbors, // A vector of neighbor indices per vertex - std::vector> &hardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary - std::vector &vertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, -); - class BlurRelax : public MPxDeformerNode { public: BlurRelax(); @@ -74,37 +62,10 @@ class BlurRelax : public MPxDeformerNode { short hbCheck = 255; short gbCheck = 255; - // storage for this data doesn't change unless the hashes do - std::vector order; - std::vector invOrder; - std::vector> neighbors; // A vector of neighbor indices per vertex - std::vector> hardEdges; // Bitwise per-neighbor data: edge is hard, edge along boundary - std::vector vertData; // Bitwise per-vert data: Group membership, geo boundary, group boundary, - std::vector shiftVal; // normally 0.5; but it's 0.25 if on a hard edge - std::vector valence; // as float for vectorizing - std::vector creaseCount; + MayaRelaxer *relaxer; MStatus getTrueWeights( - MObject &mesh, - MDataBlock &dataBlock, - UINT index, - std::vector &weightVals, - float envelope - ) const; - - void quickRelax( - MObject &mesh, - const short borderBehavior, - const short hardEdgeBehavior, - const short groupEdgeBehavior, - const bool reproject, - const float taubinBias, - const FLOAT iterations, - const UINT numVerts, - const std::vector &group, - FLOAT(*verts)[4] // already resized - ); + MObject &mesh, MDataBlock &dataBlock, UINT index, + std::vector &weightVals, float envelope) const; }; - - diff --git a/src/fastMayaRelax.cpp b/src/fastMayaRelax.cpp new file mode 100644 index 0000000..1427848 --- /dev/null +++ b/src/fastMayaRelax.cpp @@ -0,0 +1,171 @@ +#include "fastMayaRelax.h" + +MayaRelaxer::MayaRelaxer(MObject &mesh, MFnMesh &meshFn, MItGeometry& vertIter){ + std::vector> neighbors, // A vector of neighbor indices per vertex + std::vector> hardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary + std::vector vertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, + + UINT numVertices = meshFn.numVertices(); + vertData.resize(numVertices); + hardEdges.resize(numVertices); + neighbors.resize(numVertices); + + for (; !vertIter.isDone(); vertIter.next()) { + vertData[vertIter.index()] = (UCHAR)V::IN_GROUP; + } + vertIter.reset(); + + MItMeshEdge edgeIter(mesh); + for (; !edgeIter.isDone(); edgeIter.next()) { + const UINT start = edgeIter.index(0); + const UINT end = edgeIter.index(1); + + UCHAR edgeData = 0; + if (edgeIter.onBoundary()) { + edgeData |= (UCHAR)E::MESH_BORDER; + vertData[start] |= (UCHAR)V::MESH_BORDER; + vertData[end] |= (UCHAR)V::MESH_BORDER; + } + if (!edgeIter.isSmooth()) edgeData |= (UCHAR)E::HARD; + + neighbors[start].push_back(end); + neighbors[end].push_back(start); + + + if (vertData[start] & (UCHAR)V::IN_GROUP) { + if (!(vertData[end] & (UCHAR)V::IN_GROUP)) vertData[start] |= (UCHAR)V::GROUP_BORDER; + } + else if (vertData[end] & (UCHAR)V::IN_GROUP) vertData[end] |= (UCHAR)V::GROUP_BORDER; + + // an edge is a group border edge iff + // one of the two faces bordering this edge + // has a vertex that is not in the group + bool internalEdge = true; + MIntArray connFaces; + edgeIter.getConnectedFaces(connFaces); + for (UINT i=0; i2000) + for (int i = 0; i < numUnpinned; ++i) { + if ((creaseCount[i] == 0) && (group[order[i]])) { + point_t mf(verts[i][0], verts[i][1], verts[i][2]); + MPointOnMesh pom; + octree.getClosestPoint(mf, pom); + point_t gpf = pom.getPoint(); + //mfp.set(gpf, i); + verts[i][0] = gpf[0]; + verts[i][1] = gpf[1]; + verts[i][2] = gpf[2]; + } + } +} + +void MayaRelaxer::reorderVerts(MObject &mesh, MFnMesh &meshFn, FLOAT(*reoVerts)[4])const{ + // Build the raw float data buffers + pointArray_t mpa; + FLOAT(*reoVerts)[4] = new FLOAT[tNumVerts][4]; + meshFn.getPoints(mpa); + + for (size_t i = 0; i < tNumVerts; ++i) { + reoVerts[i][0] = mpa[(UINT)order[i]].x; + reoVerts[i][1] = mpa[(UINT)order[i]].y; + reoVerts[i][2] = mpa[(UINT)order[i]].z; + reoVerts[i][3] = mpa[(UINT)order[i]].w; + } +} + + +void MayaRelaxer::quickRelax( + MObject &mesh, + const bool slide, + const bool doReproject, + const float taubinBias, + const FLOAT iterations, + FLOAT(*verts)[4] +) const { + FLOAT (*baseVerts)[4]; + if (slide) { + // make a copy of the original verts only if they'll be used for edge reprojection + baseVerts = new FLOAT[numVerts][4]; + memcpy(&(baseVerts[0][0]), &(verts[0][0]), 4 * numVerts * sizeof(FLOAT)); + } + + FLOAT(*prevVerts)[4]; + prevVerts = new FLOAT[numVerts][4]; + + FLOAT iterT, iterFI; + iterT = modf(iterations, &iterFI); + UINT iterI = (UINT)iterFI; + + if (iterT > 0.0) { + iterI += 1; + } + + if (doReproject) buildOctree(mesh, slide, 1u); + + for (size_t r = 0; r < iterI; ++r) { + // Store the next-to-last iteration to interpolate with + if ((r == iterI - 1) && (iterT > 0.0)) + memcpy(&(prevVerts[0][0]), &(verts[0][0]), 4 * numVerts * sizeof(FLOAT)); + + quickLaplacianSmooth(verts); + if (taubinBias < 1.0) + quickLaplacianSmooth(verts, taubinBias); + if (slide) + edgeProject(baseVerts, verts); + if (doReproject) + reproject(verts); + } + + // Interpolate between prevVerts and verts based on iterT + if (iterT > 0.0) { + // This should vectorize + FLOAT * vv = &verts[0][0]; + FLOAT * pv = &prevVerts[0][0]; + for (size_t i = 0; i < numVerts * 4; ++i) { + vv[i] = ((vv[i] - pv[i]) * iterT) + pv[i]; + } + } + + if (slide) + delete[] baseVerts; + delete[] prevVerts; +} + diff --git a/src/fastMayaRelax.h b/src/fastMayaRelax.h new file mode 100644 index 0000000..0579194 --- /dev/null +++ b/src/fastMayaRelax.h @@ -0,0 +1,31 @@ +#include +#include "fastRelax.h" +#include +#include +#include +#include + +// Double vs Float + +typedef typename std::conditional::type point_t; +typedef typename std::conditional::type pointArray_t; + +class MayaRelaxer: public Relaxer { +public: + MMeshIntersector octree; + MObject smoothMeshPar, smoothMesh; + MayaRelaxer(MObject &mesh, MFnMesh &meshFn, MItGeometry& vertIter); + + void MayaRelaxer::quickRelax( + MObject &mesh, + const bool slide, + const bool doReproject, + const float taubinBias, + const FLOAT iterations, + FLOAT(*verts)[4] + ) const ; + void buildOctree(MObject &mesh, bool slide, UINT divisions); + void reprojectVerts(FLOAT(*verts)[4]) const; + void reorderVerts(MObject &mesh, MFnMesh &meshFn, FLOAT(*reoVerts)[4]) const; +} + diff --git a/src/fastRelax.cpp b/src/fastRelax.cpp index a52e2cf..77ceab0 100644 --- a/src/fastRelax.cpp +++ b/src/fastRelax.cpp @@ -36,14 +36,12 @@ SOFTWARE. */ -void edgeProject( + + +void Relaxer::edgeProject( const FLOAT basePoints[][NUM_COMPS], - const std::vector &group, - const std::vector &invOrder, - const std::vector> &neighbors, - const std::vector &creaseCount, FLOAT smoothPoints[][NUM_COMPS] -) { +) const { std::vector neigh; for (size_t gidx = 0; gidx < group.size(); ++gidx) { // If we have "hard edges" we have already removed @@ -127,14 +125,10 @@ void edgeProject( neighborOffsets contains the index offsets of vertices with at least [index] of neighbors */ -void quickLaplacianSmooth( +void Relaxer::quickLaplacianSmooth( FLOAT verts2d[][NUM_COMPS], - const size_t numVerts, - const std::vector> &neighbors, - const std::vector &valence, - const std::vector &shiftVal, const FLOAT taubinBias -) { +) const { // First, get verts as a single pointer to the contiguous memory stored in (verts2d*)[4] FLOAT* verts = &(verts2d[0][0]); @@ -173,53 +167,49 @@ void quickLaplacianSmooth( /* I'm trying to pre-process everything I can right here so I don't have to have any 'if's in quickLaplacianSmooth() so auto-vectorization works + + borderBehavior, // BB_NONE/BB_PIN/BB_SLIDE + hardEdgeBehavior, // HB_NONE/HB_PIN/HB_SLIDE + groupEdgeBehavior, // GB_NONE/GB_PIN/GB_SLIDE + std::vector> rawNeighbors, // A vector of neighbor indices per vertex + std::vector> rawHardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary + const std::vector &rawVertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, + */ -void fillQuickTopoVars( - // Behaviors - short borderBehavior, // BB_NONE/BB_PIN/BB_SLIDE - short hardEdgeBehavior, // HB_NONE/HB_PIN/HB_SLIDE - short groupEdgeBehavior, // GB_NONE/GB_PIN/GB_SLIDE - - // Inputs - std::vector> rawNeighbors, // A vector of neighbor indices per vertex. Copied - std::vector> rawHardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary. Copied - const std::vector &rawVertData, // Bitwise per-vert data: Group membership, geo boundary, group boundary, - - // Outputs - std::vector> &neighbors, - std::vector &creaseCount, - std::vector &shiftVal, - std::vector &valence, - std::vector &order, - std::vector &invOrder -){ - size_t numVertices = rawVertData.size(); +Relaxer::Relaxer( + short borderBehavior, + short hardEdgeBehavior, + short groupEdgeBehavior, + std::vector> rawNeighbors, // Not a reference + std::vector> rawHardEdges, // Not a reference + const std::vector &rawVertData +) { // Read the input data and define the per-point behavior - std::vector rawPinPoints; + std::vector rawPinPoints; std::vector rawCreaseCount; rawPinPoints.resize(numVertices); rawCreaseCount.resize(numVertices); for (size_t start=0; start newNeigh; - std::vector newHard; + if ((rawCreaseCount[i] != 0) || rawPinPoints[i]) { + if (rawCreaseCount[i] != 2) + rawPinPoints[i] = true; + + std::vector newNeigh; + std::vector newHard; + if (!rawPinPoints[i]) { for (size_t j = 0; j < rawNeighbors[i].size(); ++j) { - if (rawHardEdges[i][j] & E_HARD) { + if (rawHardEdges[i][j] & (UCHAR)E::HARD) { newNeigh.push_back(rawNeighbors[i][j]); newHard.push_back(rawHardEdges[i][j]); } } - rawNeighbors[i] = newNeigh; - rawHardEdges[i] = newHard; } + rawNeighbors[i] = newNeigh; + rawHardEdges[i] = newHard; + rawShiftVal[i] = 0.25; } } // Reading the data is done. Now we transform the data for fast processing - size_t maxValence = rawNeighbors[order[0]].size(); - neighbors.resize(maxValence); order.resize(numVertices); invOrder.resize(numVertices); creaseCount.resize(numVertices); @@ -269,6 +258,9 @@ void fillQuickTopoVars( std::sort(order.begin(), order.end(), [&rawNeighbors](size_t a, size_t b) {return rawNeighbors[a].size() > rawNeighbors[b].size(); }); for (size_t i = 0; i < order.size(); ++i) { invOrder[order[i]] = i; } + size_t maxValence = rawNeighbors[order[0]].size(); + neighbors.resize(maxValence); + // Build the "transposed" neighbor and hard edge values for (size_t i = 0; i < numVertices; ++i) { const auto &neigh = rawNeighbors[order[i]]; diff --git a/src/fastRelax.h b/src/fastRelax.h index 2fefc55..a0f13e0 100644 --- a/src/fastRelax.h +++ b/src/fastRelax.h @@ -26,68 +26,47 @@ SOFTWARE. typedef double FLOAT; typedef unsigned int UINT; -#define NUM_COMPS 4 - -// Yes, these should be enums. I'll get to it -#define V_IN_GROUP 1 -#define V_MESH_BORDER 2 -#define V_GROUP_BORDER 4 - -#define E_HARD 1 -#define E_MESH_BORDER 2 -#define E_GROUP_BORDER 4 - -#define BB_NONE 0 -#define BB_PIN 1 -#define BB_SLIDE 2 - -#define HB_NONE 0 -#define HB_PIN 1 -#define HB_SLIDE 2 - -#define GB_NONE 0 -#define GB_PIN 1 -#define GB_SLIDE 2 - -#define SA_LAPLACIAN 0 -#define SA_TAUBIN 1 - - -void edgeProject( - const FLOAT basePoints[][NUM_COMPS], - const std::vector &group, - const std::vector &invOrder, - const std::vector> &neighbors, - const std::vector &creaseCount, - FLOAT smoothPoints[][NUM_COMPS] -); - -void quickLaplacianSmooth( - FLOAT verts2d[][NUM_COMPS], - const size_t numVerts, - const std::vector> &neighbors, - const std::vector &valence, - const std::vector &shiftVal, - const FLOAT taubinBias=1.0 -); - -void fillQuickTopoVars( - // Behaviors - short borderBehavior, // BB_NONE/BB_PIN/BB_SLIDE - short hardEdgeBehavior, // HB_NONE/HB_PIN/HB_SLIDE - short groupEdgeBehavior, // GB_NONE/GB_PIN/GB_SLIDE - - // Inputs - std::vector> rawNeighbors, // A vector of neighbor indices per vertex. Copied - std::vector> rawHardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary. Copied - const std::vector &rawVertData, // Bitwise per-vert data: Group membership, geo boundary, group boundary, - - // Outputs - std::vector> &neighbors, - std::vector &creaseCount, - std::vector &shiftVal, - std::vector &valence, - std::vector &order, - std::vector &invOrder -); - +typedef unsigned char UCHAR; +constexpr auto NUM_COMPS = 4; + + +enum class V : UCHAR { IN_GROUP = 1, MESH_BORDER = 2, GROUP_BORDER = 4 }; +enum class E : UCHAR { HARD = 1, MESH_BORDER = 2, GROUP_BORDER = 4 }; +enum class B : UCHAR { NONE, PIN, SLIDE }; +enum class S : UCHAR { TAUBIN, LAPLACIAN }; + + +class Relaxer { +public: + std::vector order; + std::vector invOrder; + std::vector group; + std::vector> neighbors; // A vector of neighbor indices per vertex + std::vector> hardEdges; // Bitwise per-neighbor data: edge is hard, edge along boundary + std::vector vertData; // Bitwise per-vert data: Group membership, geo boundary, group boundary, + std::vector shiftVal; // normally 0.5; but it's 0.25 if on a hard edge + std::vector valence; // as float for vectorizing + std::vector creaseCount; // The number of neighboring hard edges per vert + UINT numVertices; + UINT numUnpinned; + + Relaxer( + short borderBehavior, + short hardEdgeBehavior, + short groupEdgeBehavior, + std::vector> rawNeighbors, + std::vector> rawHardEdges, + const std::vector &rawVertData + ); + + void edgeProject( + const FLOAT basePoints[][NUM_COMPS], + FLOAT smoothPoints[][NUM_COMPS] + ) const; + + void quickLaplacianSmooth( + FLOAT verts2d[][NUM_COMPS], + const FLOAT taubinBias=1.0 + ) const; + +}; From eb58dd4dfca13639711d8dc45fce4c285a86aa8b Mon Sep 17 00:00:00 2001 From: Tyler Fox Date: Thu, 12 Sep 2019 10:51:05 -0700 Subject: [PATCH 08/12] Fix the abstraction of the MayaRelaxer --- CMakeLists.txt | 2 ++ src/blurRelaxNode.h | 2 +- src/fastMayaRelax.cpp | 41 ++++++++++++++++++++++++++--------------- src/fastMayaRelax.h | 25 +++++++++++++++++++++---- 4 files changed, 50 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ec0a074..2ec60d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,8 @@ set(SOURCE_FILES "src/blurRelaxNode.h" "src/fastRelax.cpp" "src/fastRelax.h" + "src/fastMayaRelax.cpp" + "src/fastMayaRelax.h" "src/pluginRegister.cpp" ) diff --git a/src/blurRelaxNode.h b/src/blurRelaxNode.h index 44ababd..792528e 100644 --- a/src/blurRelaxNode.h +++ b/src/blurRelaxNode.h @@ -25,7 +25,7 @@ SOFTWARE. #pragma once #include #include -#include "fastRelax.h" +#include "fastMayaRelax.h" #define DEFORMER_NAME "BlurRelax" #define CHECKSTAT(m) if (!status) {status.perror(m); return status;}; diff --git a/src/fastMayaRelax.cpp b/src/fastMayaRelax.cpp index 1427848..00782dc 100644 --- a/src/fastMayaRelax.cpp +++ b/src/fastMayaRelax.cpp @@ -1,9 +1,18 @@ +#include +#include +#include + #include "fastMayaRelax.h" -MayaRelaxer::MayaRelaxer(MObject &mesh, MFnMesh &meshFn, MItGeometry& vertIter){ - std::vector> neighbors, // A vector of neighbor indices per vertex - std::vector> hardEdges, // Bitwise per-neighbor data: edge is hard, edge along boundary - std::vector vertData // Bitwise per-vert data: Group membership, geo boundary, group boundary, + +MayaRelaxer MayaRelaxer::Create( + MObject &mesh, MFnMesh &meshFn, MItGeometry& vertIter, + short borderBehavior, short hardEdgeBehavior, short groupEdgeBehavior +){ + + std::vector> neighbors; + std::vector> hardEdges; + std::vector vertData; UINT numVertices = meshFn.numVertices(); vertData.resize(numVertices); @@ -59,6 +68,8 @@ MayaRelaxer::MayaRelaxer(MObject &mesh, MFnMesh &meshFn, MItGeometry& vertIter){ hardEdges[start].push_back(edgeData); hardEdges[end].push_back(edgeData); } + + return MayaRelaxer(borderBehavior, hardEdgeBehavior, groupEdgeBehavior, neighbors, hardEdges, vertData); } void MayaRelaxer::buildOctree(MObject &mesh, bool slide, UINT divisions){ @@ -80,9 +91,9 @@ void MayaRelaxer::buildOctree(MObject &mesh, bool slide, UINT divisions){ } } -void MayaRelaxer::reproject(FLOAT(*verts)[4]) const{ +void MayaRelaxer::reprojectVerts(FLOAT(*verts)[4]) const{ if (!octree.isCreated()) return; - #pragma omp parallel for if(numVerts>2000) + #pragma omp parallel for if(numVertices>2000) for (int i = 0; i < numUnpinned; ++i) { if ((creaseCount[i] == 0) && (group[order[i]])) { point_t mf(verts[i][0], verts[i][1], verts[i][2]); @@ -100,10 +111,10 @@ void MayaRelaxer::reproject(FLOAT(*verts)[4]) const{ void MayaRelaxer::reorderVerts(MObject &mesh, MFnMesh &meshFn, FLOAT(*reoVerts)[4])const{ // Build the raw float data buffers pointArray_t mpa; - FLOAT(*reoVerts)[4] = new FLOAT[tNumVerts][4]; + FLOAT(*reoVerts)[4] = new FLOAT[numVertices][4]; meshFn.getPoints(mpa); - for (size_t i = 0; i < tNumVerts; ++i) { + for (size_t i = 0; i < numVertices; ++i) { reoVerts[i][0] = mpa[(UINT)order[i]].x; reoVerts[i][1] = mpa[(UINT)order[i]].y; reoVerts[i][2] = mpa[(UINT)order[i]].z; @@ -119,16 +130,16 @@ void MayaRelaxer::quickRelax( const float taubinBias, const FLOAT iterations, FLOAT(*verts)[4] -) const { +) { FLOAT (*baseVerts)[4]; if (slide) { // make a copy of the original verts only if they'll be used for edge reprojection - baseVerts = new FLOAT[numVerts][4]; - memcpy(&(baseVerts[0][0]), &(verts[0][0]), 4 * numVerts * sizeof(FLOAT)); + baseVerts = new FLOAT[numVertices][4]; + memcpy(&(baseVerts[0][0]), &(verts[0][0]), 4 * numVertices * sizeof(FLOAT)); } FLOAT(*prevVerts)[4]; - prevVerts = new FLOAT[numVerts][4]; + prevVerts = new FLOAT[numVertices][4]; FLOAT iterT, iterFI; iterT = modf(iterations, &iterFI); @@ -143,7 +154,7 @@ void MayaRelaxer::quickRelax( for (size_t r = 0; r < iterI; ++r) { // Store the next-to-last iteration to interpolate with if ((r == iterI - 1) && (iterT > 0.0)) - memcpy(&(prevVerts[0][0]), &(verts[0][0]), 4 * numVerts * sizeof(FLOAT)); + memcpy(&(prevVerts[0][0]), &(verts[0][0]), 4 * numVertices * sizeof(FLOAT)); quickLaplacianSmooth(verts); if (taubinBias < 1.0) @@ -151,7 +162,7 @@ void MayaRelaxer::quickRelax( if (slide) edgeProject(baseVerts, verts); if (doReproject) - reproject(verts); + reprojectVerts(verts); } // Interpolate between prevVerts and verts based on iterT @@ -159,7 +170,7 @@ void MayaRelaxer::quickRelax( // This should vectorize FLOAT * vv = &verts[0][0]; FLOAT * pv = &prevVerts[0][0]; - for (size_t i = 0; i < numVerts * 4; ++i) { + for (size_t i = 0; i < numVertices * 4; ++i) { vv[i] = ((vv[i] - pv[i]) * iterT) + pv[i]; } } diff --git a/src/fastMayaRelax.h b/src/fastMayaRelax.h index 0579194..c3667c7 100644 --- a/src/fastMayaRelax.h +++ b/src/fastMayaRelax.h @@ -1,9 +1,13 @@ #include -#include "fastRelax.h" #include #include #include #include +#include +#include +#include +#include +#include "fastRelax.h" // Double vs Float @@ -14,8 +18,21 @@ class MayaRelaxer: public Relaxer { public: MMeshIntersector octree; MObject smoothMeshPar, smoothMesh; - MayaRelaxer(MObject &mesh, MFnMesh &meshFn, MItGeometry& vertIter); + static MayaRelaxer Create(MObject &mesh, MFnMesh &meshFn, MItGeometry& vertIter, + short borderBehavior, short hardEdgeBehavior, short groupEdgeBehavior + ); + + MayaRelaxer( + short borderBehavior, + short hardEdgeBehavior, + short groupEdgeBehavior, + std::vector> rawNeighbors, + std::vector> rawHardEdges, + const std::vector &rawVertData + ) : Relaxer(borderBehavior, hardEdgeBehavior, groupEdgeBehavior, rawNeighbors, rawHardEdges, rawVertData) + { } + void MayaRelaxer::quickRelax( MObject &mesh, const bool slide, @@ -23,9 +40,9 @@ class MayaRelaxer: public Relaxer { const float taubinBias, const FLOAT iterations, FLOAT(*verts)[4] - ) const ; + ); void buildOctree(MObject &mesh, bool slide, UINT divisions); void reprojectVerts(FLOAT(*verts)[4]) const; void reorderVerts(MObject &mesh, MFnMesh &meshFn, FLOAT(*reoVerts)[4]) const; -} +}; From 9c2ac141779627672aea4025a3bc9f1f291ffa36 Mon Sep 17 00:00:00 2001 From: Tyler Fox Date: Thu, 12 Sep 2019 18:35:44 -0700 Subject: [PATCH 09/12] Finish up the simple delta code --- src/blurRelaxNode.cpp | 100 +++++++++++++++++++++------------- src/fastMayaRelax.cpp | 124 +++++++++++++++++++++++++++++++++--------- src/fastMayaRelax.h | 21 +++++-- 3 files changed, 174 insertions(+), 71 deletions(-) diff --git a/src/blurRelaxNode.cpp b/src/blurRelaxNode.cpp index c640187..0d48c5d 100644 --- a/src/blurRelaxNode.cpp +++ b/src/blurRelaxNode.cpp @@ -30,7 +30,8 @@ SOFTWARE. #include -#include +#include +#include #include #include @@ -44,6 +45,13 @@ SOFTWARE. #include #include #include +#include +#include +#include +#include + +#include +#include #include "blurRelaxNode.h" #include "fastRelax.h" @@ -296,6 +304,8 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM UINT tNumPolys = meshFn.numPolygons(); UINT tNumEdges = meshFn.numEdges(); + bool slide = (bb == (UCHAR)B::SLIDE) || (hb == (UCHAR)B::SLIDE) || (gb == (UCHAR)B::SLIDE); + if (recompute || (bbCheck != bb) || (hbCheck != hb) || (gbCheck != gb) || (tNumVerts != hNumVerts) || (tNumPolys != hNumPolys) || (tNumEdges != hNumEdges) @@ -305,13 +315,8 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM // Populate the variables with *SPECIALLY ORDERED* data // all vertex data is now shuffled by the order vector - std::vector> rawNeighbors; // A vector of neighbor indices per vertex - std::vector> rawHardEdges; // Bitwise per-neighbor data: edge is hard, edge along boundary - std::vector vertData; - loadMayaTopologyData(mesh, meshFn, vertIter, rawNeighbors, rawHardEdges, vertData); - if (relaxer != NULL) delete relaxer; - relaxer = new Relaxer(bb, hb, gb, rawNeighbors, rawHardEdges, vertData); + relaxer = MayaRelaxer::Create(mesh, meshFn, vertIter, bb, hb, gb); } // This can happen if the user is pinning all the points @@ -320,6 +325,8 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM if (relaxer->neighbors.empty()) return status; + // TODO!! Move these offsets (and the doDelta) into someplace more optimized + MPointArray offsets; if (doDelta) { // get the input mesh corresponding to this output MPlug pDeltaBase(thisMObject(), aDeltaBase); @@ -335,51 +342,66 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM MGlobal::displayError("Null delta base mesh"); return MStatus::kFailure; } - MFnMesh deltaBaseFn(deltaBase); - // Just pass the point positions from deltaBaseFn to this->relaxer + // Create a copy of the input mesh so we can have maya do the normal/tangent/binormal calculations + MFnMeshData baseCopyParFn; + MObject baseCopyPar = baseCopyParFn.create(); + MFnMesh baseCopyFn; + MObject baseCopy = baseCopyFn.copy(deltaBase, baseCopyPar); + + // get a base and smoothed copy of the original verts + pointArray_t baseVerts, smoothedBaseVerts; + baseCopyFn.getPoints(baseVerts); + smoothedBaseVerts = relaxer->quickRelax(baseCopy, slide, /*reproject*/ false, tBias, iterations); + baseCopyFn.setPoints(smoothedBaseVerts); + // TODO: Get the points in n/t/b space and store + + MItMeshVertex normIt(baseCopy); + offsets.setLength(tNumVerts); + + for (; !normIt.isDone(); normIt.next()) { + UINT idx = normIt.index(); + MMatrix mat = relaxer->getMatrixAtPoint(baseCopy, baseCopyFn, normIt); + offsets[idx] = baseVerts[idx] * mat.inverse(); + } } + // Get the painted weight values + std::vector weightVals; + status = getTrueWeights(mesh, dataBlock, multiIndex, weightVals, env); + // Get the smoothed point positions + pointArray_t mpa = relaxer->quickRelax(mesh, slide, reproject, tBias, iterations); + if (doDelta) { + MItMeshVertex deltaIt(mesh); + int prev; + point_t mp; + for (; !vertIter.isDone(); vertIter.next()) { + int idx = vertIter.index(); + deltaIt.setIndex(idx, prev); + // Get the matrix at the point + MMatrix mat = relaxer->getMatrixAtPoint(mesh, meshFn, deltaIt); - // Build the raw float data buffers - pointArray_t mpa; - FLOAT(*reoVerts)[4] = new FLOAT[tNumVerts][4]; - meshFn.getPoints(mpa); - - for (size_t i = 0; i < tNumVerts; ++i) { - reoVerts[i][0] = mpa[(UINT)order[i]].x; - reoVerts[i][1] = mpa[(UINT)order[i]].y; - reoVerts[i][2] = mpa[(UINT)order[i]].z; - reoVerts[i][3] = mpa[(UINT)order[i]].w; - } - - // Calculate the relax, and store in verts - - bool slide = (borderBehavior == (UCHAR)B::SLIDE) || (hardEdgeBehavior == (UCHAR)B::SLIDE) || (groupEdgeBehavior == (UCHAR)B::SLIDE); - quickRelax(mesh, slide, reproject, tBias, iterations, tNumVerts, vertData, reoVerts); + // multiply (offsets[idx] * deltaMultiplier) * mat; + mp = (offsets[idx] * deltaMult) * mat; - // Get the painted weight values - std::vector weightVals; - status = getTrueWeights(mesh, dataBlock, multiIndex, weightVals, env); + // then do the setPosition on this point + vertIter.setPosition((weightVals[idx]) * mp + (1.0 - weightVals[idx]) * vertIter.position()); + } - // Finally set the output - for (; !vertIter.isDone(); vertIter.next()) { - const UINT idx = vertIter.index(); - vertIter.setPosition((weightVals[idx]) * point_t(reoVerts[invOrder[idx]]) + (1.0 - weightVals[idx]) * vertIter.position()); + } + else { + for (; !vertIter.isDone(); vertIter.next()) { + int idx = vertIter.index(); + vertIter.setPosition((weightVals[idx]) * mpa[idx] + (1.0 - weightVals[idx]) * vertIter.position()); + } } - // Make sure to clean up after myself - delete [] reoVerts; + } return status; } - -/* - Load the minimal topology data from Maya -*/ - diff --git a/src/fastMayaRelax.cpp b/src/fastMayaRelax.cpp index 00782dc..e28c2f1 100644 --- a/src/fastMayaRelax.cpp +++ b/src/fastMayaRelax.cpp @@ -1,11 +1,12 @@ #include #include +#include #include #include "fastMayaRelax.h" -MayaRelaxer MayaRelaxer::Create( +MayaRelaxer* MayaRelaxer::Create( MObject &mesh, MFnMesh &meshFn, MItGeometry& vertIter, short borderBehavior, short hardEdgeBehavior, short groupEdgeBehavior ){ @@ -69,7 +70,7 @@ MayaRelaxer MayaRelaxer::Create( hardEdges[end].push_back(edgeData); } - return MayaRelaxer(borderBehavior, hardEdgeBehavior, groupEdgeBehavior, neighbors, hardEdges, vertData); + return new MayaRelaxer(borderBehavior, hardEdgeBehavior, groupEdgeBehavior, neighbors, hardEdges, vertData); } void MayaRelaxer::buildOctree(MObject &mesh, bool slide, UINT divisions){ @@ -91,7 +92,7 @@ void MayaRelaxer::buildOctree(MObject &mesh, bool slide, UINT divisions){ } } -void MayaRelaxer::reprojectVerts(FLOAT(*verts)[4]) const{ +void MayaRelaxer::reprojectVerts(FLOAT(*verts)[NUM_COMPS]) const{ if (!octree.isCreated()) return; #pragma omp parallel for if(numVertices>2000) for (int i = 0; i < numUnpinned; ++i) { @@ -108,53 +109,118 @@ void MayaRelaxer::reprojectVerts(FLOAT(*verts)[4]) const{ } } -void MayaRelaxer::reorderVerts(MObject &mesh, MFnMesh &meshFn, FLOAT(*reoVerts)[4])const{ + + +MMatrix MayaRelaxer::getMatrixAtPoint(MObject &mesh, MFnMesh &meshFn, MItMeshVertex &vertIt) const { + MVector norm, tang, binm; + MIntArray connFaces; + + + int idx = vertIt.index(); + vertIt.getNormal(norm); + vertIt.getConnectedFaces(connFaces); + meshFn.getFaceVertexTangent(connFaces[0], idx, tang); + binm = (norm ^ tang).normal(); + tang = (binm ^ norm).normal(); + MPoint vert = vertIt.position(); + + /* + double mm[4][4]; + mm[0][0] = norm[0]; mm[0][1] = tang[0]; mm[0][2] = binm[0]; mm[0][3] = 0.0; + mm[1][0] = norm[1]; mm[1][1] = tang[1]; mm[1][2] = binm[1]; mm[1][3] = 0.0; + mm[2][0] = norm[2]; mm[2][1] = tang[2]; mm[2][2] = binm[2]; mm[2][3] = 0.0; + mm[3][0] = vert[0]; mm[3][1] = vert[1]; mm[3][2] = vert[2]; mm[3][3] = 1.0; + */ + + double mm[4][4] = { + {norm[0], tang[0], binm[0], 0.0}, + {norm[1], tang[1], binm[1], 0.0}, + {norm[2], tang[2], binm[2], 0.0}, + {vert[0], vert[1], vert[2], 1.0} + }; + + MMatrix mat(mm); + return mat; +} + + + + + +void MayaRelaxer::reorderVerts(pointArray_t mpa, FLOAT(*reoVerts)[NUM_COMPS])const{ + // Build the raw float data buffers + reoVerts = new FLOAT[numVertices][NUM_COMPS]; + for (size_t i = 0; i < numVertices; ++i) { + for (size_t j = 0; j < NUM_COMPS; ++j) { + reoVerts[i][j] = mpa[(UINT)order[i]][j]; + } + } +} + +void MayaRelaxer::reorderVerts(MObject &mesh, MFnMesh &meshFn, FLOAT(*reoVerts)[NUM_COMPS]) const{ // Build the raw float data buffers pointArray_t mpa; - FLOAT(*reoVerts)[4] = new FLOAT[numVertices][4]; meshFn.getPoints(mpa); + reorderVerts(mpa, reoVerts); +} + + + + +void MayaRelaxer::revertVerts(FLOAT(*reoVerts)[NUM_COMPS], FLOAT(*reverted)[NUM_COMPS]) const{ for (size_t i = 0; i < numVertices; ++i) { - reoVerts[i][0] = mpa[(UINT)order[i]].x; - reoVerts[i][1] = mpa[(UINT)order[i]].y; - reoVerts[i][2] = mpa[(UINT)order[i]].z; - reoVerts[i][3] = mpa[(UINT)order[i]].w; + for (size_t j = 0; j < NUM_COMPS; ++j) { + reverted[i][j] = reoVerts[(UINT)invOrder[i]][j]; + } } } +pointArray_t MayaRelaxer::revertVerts(FLOAT(*reoVerts)[NUM_COMPS]) const{ + FLOAT(*rawRevert)[NUM_COMPS] = new FLOAT[numVertices][NUM_COMPS]; + revertVerts(reoVerts, rawRevert); + pointArray_t reverted(rawRevert, numVertices); + delete[] rawRevert; + return reverted; +} -void MayaRelaxer::quickRelax( +pointArray_t MayaRelaxer::quickRelax( MObject &mesh, const bool slide, const bool doReproject, const float taubinBias, - const FLOAT iterations, - FLOAT(*verts)[4] + const FLOAT iterations ) { - FLOAT (*baseVerts)[4]; - if (slide) { - // make a copy of the original verts only if they'll be used for edge reprojection - baseVerts = new FLOAT[numVertices][4]; - memcpy(&(baseVerts[0][0]), &(verts[0][0]), 4 * numVertices * sizeof(FLOAT)); - } - - FLOAT(*prevVerts)[4]; - prevVerts = new FLOAT[numVertices][4]; + // Split the float iterations into an int and float FLOAT iterT, iterFI; iterT = modf(iterations, &iterFI); UINT iterI = (UINT)iterFI; + if (iterT > 0.0) { iterI += 1; } - if (iterT > 0.0) { - iterI += 1; + // Get the mesh verts + MFnMesh meshFn(mesh); + FLOAT(*verts)[NUM_COMPS]; + reorderVerts(mesh, meshFn, verts); + + // make a copy of the original verts only if they'll be used for edge reprojection + FLOAT(*baseVerts)[NUM_COMPS]; + if (slide) { + baseVerts = new FLOAT[numVertices][NUM_COMPS]; + memcpy(&(baseVerts[0][0]), &(verts[0][0]), NUM_COMPS * numVertices * sizeof(FLOAT)); } - + + // Reserve some memory for the next-to-last iteration so we can blend + FLOAT(*prevVerts)[NUM_COMPS]; + prevVerts = new FLOAT[numVertices][NUM_COMPS]; + + // If reprojecting, then build the octree if (doReproject) buildOctree(mesh, slide, 1u); for (size_t r = 0; r < iterI; ++r) { // Store the next-to-last iteration to interpolate with if ((r == iterI - 1) && (iterT > 0.0)) - memcpy(&(prevVerts[0][0]), &(verts[0][0]), 4 * numVertices * sizeof(FLOAT)); + memcpy(&(prevVerts[0][0]), &(verts[0][0]), NUM_COMPS * numVertices * sizeof(FLOAT)); quickLaplacianSmooth(verts); if (taubinBias < 1.0) @@ -170,13 +236,19 @@ void MayaRelaxer::quickRelax( // This should vectorize FLOAT * vv = &verts[0][0]; FLOAT * pv = &prevVerts[0][0]; - for (size_t i = 0; i < numVertices * 4; ++i) { + for (size_t i = 0; i < numVertices * NUM_COMPS; ++i) { vv[i] = ((vv[i] - pv[i]) * iterT) + pv[i]; } } + pointArray_t ret = revertVerts(verts); + + // clean up after myself if (slide) delete[] baseVerts; delete[] prevVerts; + delete[] verts; + + return ret; } diff --git a/src/fastMayaRelax.h b/src/fastMayaRelax.h index c3667c7..b9ecb03 100644 --- a/src/fastMayaRelax.h +++ b/src/fastMayaRelax.h @@ -19,7 +19,7 @@ class MayaRelaxer: public Relaxer { MMeshIntersector octree; MObject smoothMeshPar, smoothMesh; - static MayaRelaxer Create(MObject &mesh, MFnMesh &meshFn, MItGeometry& vertIter, + static MayaRelaxer* Create(MObject &mesh, MFnMesh &meshFn, MItGeometry& vertIter, short borderBehavior, short hardEdgeBehavior, short groupEdgeBehavior ); @@ -33,16 +33,25 @@ class MayaRelaxer: public Relaxer { ) : Relaxer(borderBehavior, hardEdgeBehavior, groupEdgeBehavior, rawNeighbors, rawHardEdges, rawVertData) { } - void MayaRelaxer::quickRelax( + pointArray_t MayaRelaxer::quickRelax( MObject &mesh, const bool slide, const bool doReproject, const float taubinBias, - const FLOAT iterations, - FLOAT(*verts)[4] + const FLOAT iterations ); + void buildOctree(MObject &mesh, bool slide, UINT divisions); - void reprojectVerts(FLOAT(*verts)[4]) const; - void reorderVerts(MObject &mesh, MFnMesh &meshFn, FLOAT(*reoVerts)[4]) const; + void reprojectVerts(FLOAT(*verts)[NUM_COMPS]) const; + + void reorderVerts(MObject &mesh, MFnMesh &meshFn, FLOAT(*reoVerts)[NUM_COMPS]) const; + void reorderVerts(pointArray_t mpa, FLOAT(*reoVerts)[NUM_COMPS]) const; + + void revertVerts(FLOAT(*reoVerts)[NUM_COMPS], FLOAT(*reverted)[NUM_COMPS]) const; + pointArray_t revertVerts(FLOAT(*reoVerts)[NUM_COMPS]) const; + + + MMatrix getMatrixAtPoint(MObject &mesh, MFnMesh &meshFn, MItMeshVertex &vertIt) const; + }; From cecf9d2809dfd1deb4b4742c501723f3ca01944c Mon Sep 17 00:00:00 2001 From: Tyler Fox Date: Fri, 13 Sep 2019 13:43:45 -0700 Subject: [PATCH 10/12] Got everything working except Maya --- src/blurRelaxNode.cpp | 33 +++++++++++++++++++-------------- src/blurRelaxNode.h | 3 ++- src/fastMayaRelax.cpp | 18 ++++-------------- src/fastRelax.cpp | 9 +++++---- 4 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/blurRelaxNode.cpp b/src/blurRelaxNode.cpp index 0d48c5d..354133f 100644 --- a/src/blurRelaxNode.cpp +++ b/src/blurRelaxNode.cpp @@ -40,6 +40,7 @@ SOFTWARE. #include #include #include +#include #include #include @@ -315,18 +316,17 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM // Populate the variables with *SPECIALLY ORDERED* data // all vertex data is now shuffled by the order vector - if (relaxer != NULL) delete relaxer; + if (relaxer != nullptr) delete relaxer; relaxer = MayaRelaxer::Create(mesh, meshFn, vertIter, bb, hb, gb); } // This can happen if the user is pinning all the points // or all the edges are hard (like when you import an obj) - if (relaxer == NULL) return status; + if (relaxer == nullptr) return status; if (relaxer->neighbors.empty()) return status; // TODO!! Move these offsets (and the doDelta) into someplace more optimized - MPointArray offsets; if (doDelta) { // get the input mesh corresponding to this output MPlug pDeltaBase(thisMObject(), aDeltaBase); @@ -338,7 +338,7 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM return status; } MObject deltaBase = hDeltaBase.asMesh(); - if (!deltaBase.isNull()) { + if (deltaBase.isNull()) { MGlobal::displayError("Null delta base mesh"); return MStatus::kFailure; } @@ -357,14 +357,17 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM // TODO: Get the points in n/t/b space and store MItMeshVertex normIt(baseCopy); - offsets.setLength(tNumVerts); + deltas.setLength(tNumVerts); for (; !normIt.isDone(); normIt.next()) { UINT idx = normIt.index(); MMatrix mat = relaxer->getMatrixAtPoint(baseCopy, baseCopyFn, normIt); - offsets[idx] = baseVerts[idx] * mat.inverse(); + deltas[idx] = baseVerts[idx] * mat.inverse(); } } + else { + deltas.setLength(0); + } // Get the painted weight values std::vector weightVals; @@ -374,25 +377,29 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM pointArray_t mpa = relaxer->quickRelax(mesh, slide, reproject, tBias, iterations); if (doDelta) { + if (deltas.length() != tNumVerts) + return MStatus::kFailure; + + //vertIter.reset(); + //meshFn.updateSurface(); + //meshFn.cleanupEdgeSmoothing(); MItMeshVertex deltaIt(mesh); int prev; point_t mp; - for (; !vertIter.isDone(); vertIter.next()) { int idx = vertIter.index(); deltaIt.setIndex(idx, prev); // Get the matrix at the point MMatrix mat = relaxer->getMatrixAtPoint(mesh, meshFn, deltaIt); - - // multiply (offsets[idx] * deltaMultiplier) * mat; - mp = (offsets[idx] * deltaMult) * mat; + //mp = (deltas[idx] * deltaMult) * mat; + point_t zero; + mp = zero * mat; // then do the setPosition on this point - vertIter.setPosition((weightVals[idx]) * mp + (1.0 - weightVals[idx]) * vertIter.position()); + vertIter.setPosition(mp); } - } else { for (; !vertIter.isDone(); vertIter.next()) { @@ -400,8 +407,6 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM vertIter.setPosition((weightVals[idx]) * mpa[idx] + (1.0 - weightVals[idx]) * vertIter.position()); } } - - } return status; } diff --git a/src/blurRelaxNode.h b/src/blurRelaxNode.h index 792528e..280b684 100644 --- a/src/blurRelaxNode.h +++ b/src/blurRelaxNode.h @@ -62,7 +62,8 @@ class BlurRelax : public MPxDeformerNode { short hbCheck = 255; short gbCheck = 255; - MayaRelaxer *relaxer; + MPointArray deltas; + MayaRelaxer *relaxer = nullptr; MStatus getTrueWeights( MObject &mesh, MDataBlock &dataBlock, UINT index, diff --git a/src/fastMayaRelax.cpp b/src/fastMayaRelax.cpp index e28c2f1..c1e553e 100644 --- a/src/fastMayaRelax.cpp +++ b/src/fastMayaRelax.cpp @@ -95,7 +95,7 @@ void MayaRelaxer::buildOctree(MObject &mesh, bool slide, UINT divisions){ void MayaRelaxer::reprojectVerts(FLOAT(*verts)[NUM_COMPS]) const{ if (!octree.isCreated()) return; #pragma omp parallel for if(numVertices>2000) - for (int i = 0; i < numUnpinned; ++i) { + for (UINT i = 0; i < numUnpinned; ++i) { if ((creaseCount[i] == 0) && (group[order[i]])) { point_t mf(verts[i][0], verts[i][1], verts[i][2]); MPointOnMesh pom; @@ -143,16 +143,11 @@ MMatrix MayaRelaxer::getMatrixAtPoint(MObject &mesh, MFnMesh &meshFn, MItMeshVer return mat; } - - - - void MayaRelaxer::reorderVerts(pointArray_t mpa, FLOAT(*reoVerts)[NUM_COMPS])const{ // Build the raw float data buffers - reoVerts = new FLOAT[numVertices][NUM_COMPS]; for (size_t i = 0; i < numVertices; ++i) { for (size_t j = 0; j < NUM_COMPS; ++j) { - reoVerts[i][j] = mpa[(UINT)order[i]][j]; + reoVerts[i][j] = mpa[(UINT)order[i]][(UINT)j]; } } } @@ -164,10 +159,6 @@ void MayaRelaxer::reorderVerts(MObject &mesh, MFnMesh &meshFn, FLOAT(*reoVerts)[ reorderVerts(mpa, reoVerts); } - - - - void MayaRelaxer::revertVerts(FLOAT(*reoVerts)[NUM_COMPS], FLOAT(*reverted)[NUM_COMPS]) const{ for (size_t i = 0; i < numVertices; ++i) { for (size_t j = 0; j < NUM_COMPS; ++j) { @@ -200,7 +191,7 @@ pointArray_t MayaRelaxer::quickRelax( // Get the mesh verts MFnMesh meshFn(mesh); - FLOAT(*verts)[NUM_COMPS]; + FLOAT(*verts)[NUM_COMPS] = new FLOAT[numVertices][NUM_COMPS]; reorderVerts(mesh, meshFn, verts); // make a copy of the original verts only if they'll be used for edge reprojection @@ -211,8 +202,7 @@ pointArray_t MayaRelaxer::quickRelax( } // Reserve some memory for the next-to-last iteration so we can blend - FLOAT(*prevVerts)[NUM_COMPS]; - prevVerts = new FLOAT[numVertices][NUM_COMPS]; + FLOAT(*prevVerts)[NUM_COMPS] = new FLOAT[numVertices][NUM_COMPS]; // If reprojecting, then build the octree if (doReproject) buildOctree(mesh, slide, 1u); diff --git a/src/fastRelax.cpp b/src/fastRelax.cpp index 77ceab0..cd26e01 100644 --- a/src/fastRelax.cpp +++ b/src/fastRelax.cpp @@ -136,7 +136,7 @@ void Relaxer::quickLaplacianSmooth( size_t nzv = neighbors[0].size(); // number of nonzero components - size_t nzc = 4 * nzv; + size_t nzc = NUM_COMPS * nzv; // The __restrict keyword tells the compiler that *outComp // is not pointed to by any other pointer in this scope @@ -148,9 +148,9 @@ void Relaxer::quickLaplacianSmooth( const auto &nCol = neighbors[ncIdx]; size_t nColCount = nCol.size(); for (size_t i = 0; i < nColCount; ++i) { - size_t nci = 4 * nCol[i]; - for (size_t j = 0; j < 4; ++j) { - outComp[4*i+j] = outComp[4*i+j] + verts[nci+j]; + size_t nci = NUM_COMPS * nCol[i]; + for (size_t j = 0; j < NUM_COMPS; ++j) { + outComp[NUM_COMPS*i+j] = outComp[NUM_COMPS*i+j] + verts[nci+j]; } } } @@ -186,6 +186,7 @@ Relaxer::Relaxer( ) { // Read the input data and define the per-point behavior + numVertices = (UINT)rawNeighbors.size(); std::vector rawPinPoints; std::vector rawCreaseCount; rawPinPoints.resize(numVertices); From a99b524adfceca74adc7fa0a212b1f2432bc94d7 Mon Sep 17 00:00:00 2001 From: Tyler Fox Date: Fri, 13 Sep 2019 17:39:58 -0700 Subject: [PATCH 11/12] Got the delta mush working at a usable speed --- src/blurRelaxNode.cpp | 70 ++++++++++-------- src/blurRelaxNode.h | 4 +- src/fastMayaRelax.cpp | 164 ++++++++++++++++++++++++++++-------------- src/fastMayaRelax.h | 8 ++- src/fastRelax.cpp | 13 ++-- src/fastRelax.h | 2 +- 6 files changed, 168 insertions(+), 93 deletions(-) diff --git a/src/blurRelaxNode.cpp b/src/blurRelaxNode.cpp index 354133f..f30ec6e 100644 --- a/src/blurRelaxNode.cpp +++ b/src/blurRelaxNode.cpp @@ -41,6 +41,7 @@ SOFTWARE. #include #include #include +#include #include #include @@ -66,6 +67,7 @@ MObject BlurRelax::aBorderBehavior; MObject BlurRelax::aHardEdgeBehavior; MObject BlurRelax::aGroupEdgeBehavior; MObject BlurRelax::aReproject; +MObject BlurRelax::aReprojectDivs; MObject BlurRelax::aTaubinBias; MObject BlurRelax::aRecomputeTopo; MObject BlurRelax::aDeltaMush; @@ -188,6 +190,16 @@ MStatus BlurRelax::initialize() { status = attributeAffects(aReproject, outputGeom); CHECKSTAT("aReproject"); + aReprojectDivs = nAttr.create("reprojectDivs", "rpd", MFnNumericData::kInt, 1, &status); + CHECKSTAT("aReprojectDivs"); + nAttr.setMin(0); + nAttr.setSoftMax(3); + nAttr.setChannelBox(true); + status = addAttribute(aReprojectDivs); + CHECKSTAT("aReprojectDivs"); + status = attributeAffects(aReprojectDivs, outputGeom); + CHECKSTAT("aReprojectDivs"); + // Taubin Bias is divided by 1000 internally aTaubinBias = nAttr.create("preserveVolume", "pv", MFnNumericData::kFloat, 0.0f, &status); CHECKSTAT("aTaubinBias"); @@ -267,6 +279,10 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM short gb = hGroupEdge.asShort(); MDataHandle hReproject = dataBlock.inputValue(aReproject); bool reproject = hReproject.asBool(); + + MDataHandle hReprojectDivs = dataBlock.inputValue(aReprojectDivs); + int reproDivs = hReprojectDivs.asInt(); + MDataHandle hRecompute = dataBlock.inputValue(aRecomputeTopo); bool recompute = hRecompute.asBool(); @@ -293,11 +309,13 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM // and that value looked good on my test mesh. tBias = -2.05f * tBias + 1.0f; - // get the input mesh corresponding to this output - MPlug inPlug(thisMObject(), input); - inPlug.selectAncestorLogicalIndex(multiIndex, input); - MDataHandle hInput = dataBlock.inputValue(inPlug); - MObject mesh = hInput.asMesh(); + // Maya automatically copies the input plug to the output plug + // and then gives you an iterator over that + // So get the OUTPUT mesh corresponding to the mutiIndex + MPlug outPlug(thisMObject(), outputGeom); + outPlug.selectAncestorLogicalIndex(multiIndex, outputGeom); + MDataHandle hOutput = dataBlock.outputValue(outPlug); + MObject mesh = hOutput.asMesh(); // Get the point values MFnMesh meshFn(mesh); @@ -328,6 +346,7 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM // TODO!! Move these offsets (and the doDelta) into someplace more optimized if (doDelta) { + // get the input mesh corresponding to this output MPlug pDeltaBase(thisMObject(), aDeltaBase); status = pDeltaBase.selectAncestorLogicalIndex(multiIndex, aDeltaBase); @@ -352,21 +371,22 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM // get a base and smoothed copy of the original verts pointArray_t baseVerts, smoothedBaseVerts; baseCopyFn.getPoints(baseVerts); - smoothedBaseVerts = relaxer->quickRelax(baseCopy, slide, /*reproject*/ false, tBias, iterations); + bool deltaRepro = false; + UINT deltaReproDivs = 0; + smoothedBaseVerts = relaxer->quickRelax(baseCopy, slide, deltaRepro, deltaReproDivs, tBias, iterations); baseCopyFn.setPoints(smoothedBaseVerts); // TODO: Get the points in n/t/b space and store + std::vector mats = relaxer->getVertMatrices(baseCopy, baseCopyFn); MItMeshVertex normIt(baseCopy); - deltas.setLength(tNumVerts); - + deltas.resize(tNumVerts); for (; !normIt.isDone(); normIt.next()) { UINT idx = normIt.index(); - MMatrix mat = relaxer->getMatrixAtPoint(baseCopy, baseCopyFn, normIt); - deltas[idx] = baseVerts[idx] * mat.inverse(); + deltas[idx] = baseVerts[idx] * mats[idx].inverse(); } } else { - deltas.setLength(0); + deltas.resize(0); } // Get the painted weight values @@ -374,31 +394,25 @@ MStatus BlurRelax::deform(MDataBlock& dataBlock, MItGeometry& vertIter, const MM status = getTrueWeights(mesh, dataBlock, multiIndex, weightVals, env); // Get the smoothed point positions - pointArray_t mpa = relaxer->quickRelax(mesh, slide, reproject, tBias, iterations); + pointArray_t mpa = relaxer->quickRelax(mesh, slide, reproject, reproDivs, tBias, iterations); + if (doDelta) { - if (deltas.length() != tNumVerts) + if (deltas.size() != tNumVerts) return MStatus::kFailure; - //vertIter.reset(); - //meshFn.updateSurface(); - //meshFn.cleanupEdgeSmoothing(); - - MItMeshVertex deltaIt(mesh); - int prev; - point_t mp; for (; !vertIter.isDone(); vertIter.next()) { - int idx = vertIter.index(); - deltaIt.setIndex(idx, prev); + vertIter.setPosition(mpa[vertIter.index()]); + } + vertIter.reset(); - // Get the matrix at the point - MMatrix mat = relaxer->getMatrixAtPoint(mesh, meshFn, deltaIt); - //mp = (deltas[idx] * deltaMult) * mat; - point_t zero; - mp = zero * mat; + std::vector mats = relaxer->getVertMatrices(mesh, meshFn); + for (; !vertIter.isDone(); vertIter.next()) { + int idx = vertIter.index(); + point_t mp = (deltas[idx] * deltaMult) * mats[idx]; // then do the setPosition on this point - vertIter.setPosition(mp); + vertIter.setPosition((weightVals[idx]) * mp + (1.0 - weightVals[idx]) * vertIter.position()); } } else { diff --git a/src/blurRelaxNode.h b/src/blurRelaxNode.h index 280b684..14173c1 100644 --- a/src/blurRelaxNode.h +++ b/src/blurRelaxNode.h @@ -47,6 +47,7 @@ class BlurRelax : public MPxDeformerNode { static MObject aHardEdgeBehavior; static MObject aGroupEdgeBehavior; static MObject aReproject; + static MObject aReprojectDivs; static MObject aTaubinBias; static MObject aRecomputeTopo; static MObject aDeltaMush; @@ -62,7 +63,8 @@ class BlurRelax : public MPxDeformerNode { short hbCheck = 255; short gbCheck = 255; - MPointArray deltas; + MPointArray deltas2; + std::vector deltas; MayaRelaxer *relaxer = nullptr; MStatus getTrueWeights( diff --git a/src/fastMayaRelax.cpp b/src/fastMayaRelax.cpp index c1e553e..04d07d8 100644 --- a/src/fastMayaRelax.cpp +++ b/src/fastMayaRelax.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "fastMayaRelax.h" @@ -13,15 +14,15 @@ MayaRelaxer* MayaRelaxer::Create( std::vector> neighbors; std::vector> hardEdges; - std::vector vertData; + std::vector rawVertData; UINT numVertices = meshFn.numVertices(); - vertData.resize(numVertices); + rawVertData.resize(numVertices); hardEdges.resize(numVertices); neighbors.resize(numVertices); for (; !vertIter.isDone(); vertIter.next()) { - vertData[vertIter.index()] = (UCHAR)V::IN_GROUP; + rawVertData[vertIter.index()] = (UCHAR)V::IN_GROUP; } vertIter.reset(); @@ -33,8 +34,8 @@ MayaRelaxer* MayaRelaxer::Create( UCHAR edgeData = 0; if (edgeIter.onBoundary()) { edgeData |= (UCHAR)E::MESH_BORDER; - vertData[start] |= (UCHAR)V::MESH_BORDER; - vertData[end] |= (UCHAR)V::MESH_BORDER; + rawVertData[start] |= (UCHAR)V::MESH_BORDER; + rawVertData[end] |= (UCHAR)V::MESH_BORDER; } if (!edgeIter.isSmooth()) edgeData |= (UCHAR)E::HARD; @@ -42,10 +43,10 @@ MayaRelaxer* MayaRelaxer::Create( neighbors[end].push_back(start); - if (vertData[start] & (UCHAR)V::IN_GROUP) { - if (!(vertData[end] & (UCHAR)V::IN_GROUP)) vertData[start] |= (UCHAR)V::GROUP_BORDER; + if (rawVertData[start] & (UCHAR)V::IN_GROUP) { + if (!(rawVertData[end] & (UCHAR)V::IN_GROUP)) rawVertData[start] |= (UCHAR)V::GROUP_BORDER; } - else if (vertData[end] & (UCHAR)V::IN_GROUP) vertData[end] |= (UCHAR)V::GROUP_BORDER; + else if (rawVertData[end] & (UCHAR)V::IN_GROUP) rawVertData[end] |= (UCHAR)V::GROUP_BORDER; // an edge is a group border edge iff // one of the two faces bordering this edge @@ -57,7 +58,7 @@ MayaRelaxer* MayaRelaxer::Create( MIntArray polyVerts; meshFn.getPolygonVertices(connFaces[i], polyVerts); for (UINT j=0; j MayaRelaxer::getVertMatrices(MObject &mesh, MFnMesh &meshFn) const { + MStatus stat; + UINT numVerts = meshFn.numVertices(); + + MFloatVectorArray norms, faceTangs, tangs; + MFloatPointArray points; + meshFn.getVertexNormals(false, norms); + meshFn.getTangents(faceTangs); + meshFn.getPoints(points); + + tangs.setLength(numVerts); + + MIntArray counts, connects; + meshFn.getVertices(counts, connects); + + int offset = 0; + std::vector dones(numVerts); + for (UINT faceIdx = 0; faceIdx < counts.length(); ++faceIdx) { + int count = counts[faceIdx]; + + for (int i = 0; i < count; ++i) { + int vIdx = connects[offset + i]; + if (!dones[vIdx]) { + dones[vIdx] = 1; + tangs[vIdx] = faceTangs[offset + i]; + } + } + offset += count; } -} -void MayaRelaxer::reprojectVerts(FLOAT(*verts)[NUM_COMPS]) const{ - if (!octree.isCreated()) return; - #pragma omp parallel for if(numVertices>2000) - for (UINT i = 0; i < numUnpinned; ++i) { - if ((creaseCount[i] == 0) && (group[order[i]])) { - point_t mf(verts[i][0], verts[i][1], verts[i][2]); - MPointOnMesh pom; - octree.getClosestPoint(mf, pom); - point_t gpf = pom.getPoint(); - //mfp.set(gpf, i); - verts[i][0] = gpf[0]; - verts[i][1] = gpf[1]; - verts[i][2] = gpf[2]; - } + std::vector out(numVerts); + for (UINT i = 0; i < numVerts; ++i) { + MFloatPoint &vert = points[i]; + MFloatVector &norm = norms[i]; + MFloatVector &tang = tangs[i]; + + MFloatVector binm = (norm ^ tang).normal(); + tang = (binm ^ norm).normal(); + + double mm[4][4] = { + {norm[0], tang[0], binm[0], 0.0}, + {norm[1], tang[1], binm[1], 0.0}, + {norm[2], tang[2], binm[2], 0.0}, + {vert[0], vert[1], vert[2], 1.0} + }; + out[i] = MMatrix(mm); } + + return out; } MMatrix MayaRelaxer::getMatrixAtPoint(MObject &mesh, MFnMesh &meshFn, MItMeshVertex &vertIt) const { MVector norm, tang, binm; - MIntArray connFaces; - int idx = vertIt.index(); vertIt.getNormal(norm); - vertIt.getConnectedFaces(connFaces); - meshFn.getFaceVertexTangent(connFaces[0], idx, tang); + meshFn.getFaceVertexTangent(connFaces[idx], idx, tang); binm = (norm ^ tang).normal(); tang = (binm ^ norm).normal(); MPoint vert = vertIt.position(); - /* - double mm[4][4]; - mm[0][0] = norm[0]; mm[0][1] = tang[0]; mm[0][2] = binm[0]; mm[0][3] = 0.0; - mm[1][0] = norm[1]; mm[1][1] = tang[1]; mm[1][2] = binm[1]; mm[1][3] = 0.0; - mm[2][0] = norm[2]; mm[2][1] = tang[2]; mm[2][2] = binm[2]; mm[2][3] = 0.0; - mm[3][0] = vert[0]; mm[3][1] = vert[1]; mm[3][2] = vert[2]; mm[3][3] = 1.0; - */ - double mm[4][4] = { {norm[0], tang[0], binm[0], 0.0}, {norm[1], tang[1], binm[1], 0.0}, @@ -175,10 +180,39 @@ pointArray_t MayaRelaxer::revertVerts(FLOAT(*reoVerts)[NUM_COMPS]) const{ return reverted; } + + + +void MayaRelaxer::buildOctree(MObject &mesh, bool slide, UINT divisions){ +} + +void MayaRelaxer::reprojectVerts(FLOAT(*verts)[NUM_COMPS], MMeshIntersector &octree) const{ + if (!octree.isCreated()) return; + UINT nzv = (UINT)neighbors[0].size(); + #pragma omp parallel for if(numVertices>2000) + for (UINT i = 0; i < nzv; ++i) { + if ((creaseCount[i] == 0) && (vertData[i] && (UCHAR)V::IN_GROUP)) { + point_t mf(verts[i][0], verts[i][1], verts[i][2]); + MPointOnMesh pom; + octree.getClosestPoint(mf, pom); + point_t gpf = pom.getPoint(); + //mfp.set(gpf, i); + verts[i][0] = gpf[0]; + verts[i][1] = gpf[1]; + verts[i][2] = gpf[2]; + } + } +} + + + + + pointArray_t MayaRelaxer::quickRelax( MObject &mesh, const bool slide, const bool doReproject, + const UINT reprojectDivisons, const float taubinBias, const FLOAT iterations ) { @@ -205,7 +239,27 @@ pointArray_t MayaRelaxer::quickRelax( FLOAT(*prevVerts)[NUM_COMPS] = new FLOAT[numVertices][NUM_COMPS]; // If reprojecting, then build the octree - if (doReproject) buildOctree(mesh, slide, 1u); + MMeshIntersector octree; + MObject smoothMeshPar, smoothMesh; + if (doReproject) { + MStatus status; + int divisions = 0; + + if (reprojectDivisons == 0){ + octree.create(mesh); + } + else { + MFnMesh meshFn(mesh); + MFnMeshData smoothMeshParFn; + MMeshSmoothOptions smoothOpt; + smoothMeshPar = smoothMeshParFn.create(); + smoothOpt.setDivisions(reprojectDivisons); + smoothOpt.setKeepBorderEdge(slide); + smoothOpt.setSubdivisionType(MMeshSmoothOptions::kCatmullClark); + smoothMesh = meshFn.generateSmoothMesh(smoothMeshPar, &smoothOpt); + octree.create(smoothMesh); + } + } for (size_t r = 0; r < iterI; ++r) { // Store the next-to-last iteration to interpolate with @@ -218,7 +272,7 @@ pointArray_t MayaRelaxer::quickRelax( if (slide) edgeProject(baseVerts, verts); if (doReproject) - reprojectVerts(verts); + reprojectVerts(verts, octree); } // Interpolate between prevVerts and verts based on iterT diff --git a/src/fastMayaRelax.h b/src/fastMayaRelax.h index b9ecb03..ea5116d 100644 --- a/src/fastMayaRelax.h +++ b/src/fastMayaRelax.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "fastRelax.h" // Double vs Float @@ -16,8 +17,7 @@ typedef typename std::conditional connFaces; static MayaRelaxer* Create(MObject &mesh, MFnMesh &meshFn, MItGeometry& vertIter, short borderBehavior, short hardEdgeBehavior, short groupEdgeBehavior @@ -37,12 +37,13 @@ class MayaRelaxer: public Relaxer { MObject &mesh, const bool slide, const bool doReproject, + const UINT reprojectDivisons, const float taubinBias, const FLOAT iterations ); void buildOctree(MObject &mesh, bool slide, UINT divisions); - void reprojectVerts(FLOAT(*verts)[NUM_COMPS]) const; + void reprojectVerts(FLOAT(*verts)[NUM_COMPS], MMeshIntersector &octree) const; void reorderVerts(MObject &mesh, MFnMesh &meshFn, FLOAT(*reoVerts)[NUM_COMPS]) const; void reorderVerts(pointArray_t mpa, FLOAT(*reoVerts)[NUM_COMPS]) const; @@ -52,6 +53,7 @@ class MayaRelaxer: public Relaxer { MMatrix getMatrixAtPoint(MObject &mesh, MFnMesh &meshFn, MItMeshVertex &vertIt) const; + std::vector MayaRelaxer::getVertMatrices(MObject &mesh, MFnMesh &meshFn) const; }; diff --git a/src/fastRelax.cpp b/src/fastRelax.cpp index cd26e01..11e5fec 100644 --- a/src/fastRelax.cpp +++ b/src/fastRelax.cpp @@ -35,20 +35,17 @@ SOFTWARE. strings of edges. But for now, it's just direct neighbors */ - - - void Relaxer::edgeProject( const FLOAT basePoints[][NUM_COMPS], FLOAT smoothPoints[][NUM_COMPS] ) const { std::vector neigh; - for (size_t gidx = 0; gidx < group.size(); ++gidx) { + for (size_t gidx = 0; gidx < groupIdxs.size(); ++gidx) { // If we have "hard edges" we have already removed // any soft edges, so if the crease count for this vertex is zero // we can just completely skip it. And if it's >0, then we can // assume that all stored neighbors are hard. - size_t idx = invOrder[group[gidx]]; + size_t idx = invOrder[groupIdxs[gidx]]; FLOAT *avg = smoothPoints[idx]; const FLOAT *basePos = basePoints[idx]; @@ -250,6 +247,8 @@ Relaxer::Relaxer( order.resize(numVertices); invOrder.resize(numVertices); creaseCount.resize(numVertices); + vertData.resize(numVertices); + groupIdxs.resize(numVertices); shiftVal.resize(numVertices * NUM_COMPS); valence.resize(numVertices * NUM_COMPS); @@ -273,6 +272,10 @@ Relaxer::Relaxer( // only needed for slide and reproject // and I only need to know if it's ==0 or ==2 creaseCount[i] = rawCreaseCount[order[i]]; + vertData[i] = rawVertData[order[i]]; + + if (vertData[i] & (UCHAR)V::IN_GROUP) + groupIdxs.push_back((size_t)i); // Vectorizing flattens the vert list, so I need this data per vert, per component // Maya uses xyzw points, so I need 4. In other cases I'll need 3 diff --git a/src/fastRelax.h b/src/fastRelax.h index a0f13e0..5777b1a 100644 --- a/src/fastRelax.h +++ b/src/fastRelax.h @@ -40,7 +40,7 @@ class Relaxer { public: std::vector order; std::vector invOrder; - std::vector group; + std::vector groupIdxs; std::vector> neighbors; // A vector of neighbor indices per vertex std::vector> hardEdges; // Bitwise per-neighbor data: edge is hard, edge along boundary std::vector vertData; // Bitwise per-vert data: Group membership, geo boundary, group boundary, From a0468cb97a8bedb18939d8315413372c148cb16d Mon Sep 17 00:00:00 2001 From: Tyler Fox Date: Wed, 8 Jan 2020 12:15:24 -0800 Subject: [PATCH 12/12] Fix the duplicate attributes --- src/blurRelaxNode.cpp | 3 --- src/pluginRegister.cpp | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/blurRelaxNode.cpp b/src/blurRelaxNode.cpp index f30ec6e..95473a7 100644 --- a/src/blurRelaxNode.cpp +++ b/src/blurRelaxNode.cpp @@ -254,9 +254,6 @@ MStatus BlurRelax::initialize() { status = attributeAffects(aDeltaBase, outputGeom); CHECKSTAT("aDeltaBase"); - - MGlobal::executeCommand("makePaintable -attrType \"multiFloat\" -sm \"deformer\" \"" DEFORMER_NAME "\" \"weights\";"); - return MStatus::kSuccess; } diff --git a/src/pluginRegister.cpp b/src/pluginRegister.cpp index f5b1edc..c433955 100644 --- a/src/pluginRegister.cpp +++ b/src/pluginRegister.cpp @@ -23,12 +23,14 @@ SOFTWARE. */ #include +#include #include "blurRelaxNode.h" MStatus initializePlugin(MObject obj) { MStatus result; MFnPlugin plugin(obj, "Blur Studio", "1.0", "Any"); result = plugin.registerNode(DEFORMER_NAME, BlurRelax::id, BlurRelax::creator, BlurRelax::initialize, MPxNode::kDeformerNode); + MGlobal::executeCommand("makePaintable -attrType \"multiFloat\" -sm \"deformer\" \"" DEFORMER_NAME "\" \"weights\";"); return result; }