├── scripts ├── cvwrap │ ├── __init__.py │ ├── bindui.py │ └── menu.py └── AEcvWrapTemplate.mel ├── module.txt ├── .gitmodules ├── .gitignore ├── CMakeLists.txt ├── src ├── CMakeLists.txt ├── pluginMain.cpp ├── bindingio.h ├── cvWrapDeformer.h ├── cvWrapCmd.h ├── bindingio.cpp ├── common.h ├── cvwrap_pre2018.cl ├── cvwrap.cl ├── common.cpp ├── cvWrapCmd.cpp └── cvWrapDeformer.cpp ├── LICENSE └── README.md /scripts/cvwrap/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /module.txt: -------------------------------------------------------------------------------- 1 | + ${PROJECT_NAME} 1.0.0 ${PROJECT_PATH} 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "cgcmake"] 2 | path = cgcmake 3 | url = git@github.com:chadmv/cgcmake.git 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #ignore thumbnails created by windows 3 | Thumbs.db 4 | #Ignore files build by Visual Studio 5 | *.obj 6 | *.exe 7 | *.pdb 8 | *.user 9 | *.aps 10 | *.pch 11 | *.vspscc 12 | *_i.c 13 | *_p.c 14 | *.ncb 15 | *.suo 16 | *.tlb 17 | *.tlh 18 | *.bak 19 | *.cache 20 | *.ilk 21 | *.log 22 | [Bb]in 23 | [Dd]ebug*/ 24 | *.lib 25 | *.sbr 26 | obj/ 27 | [Rr]elease*/ 28 | _ReSharper*/ 29 | [Bb]uild*/ 30 | [Tt]est[Rr]esult* 31 | .idea 32 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | project(cvwrap) 3 | 4 | set(PROJECT_PATH ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT}) 5 | set(CMAKE_INSTALL_PREFIX ${PROJECT_PATH}) 6 | set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cgcmake/modules) 7 | 8 | add_subdirectory(src) 9 | 10 | configure_file("${CMAKE_CURRENT_SOURCE_DIR}/module.txt" "${PROJECT_PATH}/${PROJECT_NAME}.txt") 11 | 12 | install(DIRECTORY scripts DESTINATION .) 13 | -------------------------------------------------------------------------------- /scripts/AEcvWrapTemplate.mel: -------------------------------------------------------------------------------- 1 | global proc AEcvWrapTemplate(string $nodeName) { 2 | editorTemplate -beginScrollLayout; 3 | editorTemplate -beginLayout "cvWrap Attributes" -collapse false; 4 | editorTemplate -beginNoOptimize; 5 | editorTemplate -addControl "scale"; 6 | editorTemplate -endNoOptimize; 7 | 8 | editorTemplate -suppress "driver"; 9 | editorTemplate -suppress "bindMesh"; 10 | editorTemplate -suppress "bindData"; 11 | editorTemplate -suppress "numTasks"; 12 | editorTemplate -endLayout; 13 | 14 | AEweightGeometryFilterTemplate $nodeName; 15 | 16 | editorTemplate -addExtraControls; 17 | editorTemplate -endScrollLayout; 18 | } 19 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SOURCE_FILES 2 | "pluginMain.cpp" 3 | "cvWrapCmd.cpp" 4 | "cvWrapCmd.h" 5 | "cvWrapDeformer.cpp" 6 | "cvWrapDeformer.h" 7 | "bindingio.cpp" 8 | "bindingio.h" 9 | "common.cpp" 10 | "common.h" 11 | "cvwrap.cl" 12 | ) 13 | 14 | if (WIN32) 15 | set(COMPILE_FLAGS "/arch:AVX") 16 | else() 17 | set(COMPILE_FLAGS "-mavx") 18 | endif() 19 | 20 | find_package(Maya REQUIRED) 21 | 22 | add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) 23 | target_link_libraries(${PROJECT_NAME} PRIVATE Maya::Maya) 24 | target_include_directories(${PROJECT_NAME} PRIVATE Maya::Maya) 25 | target_compile_options(${PROJECT_NAME} PRIVATE ${COMPILE_FLAGS}) 26 | MAYA_PLUGIN(${PROJECT_NAME}) 27 | 28 | install(TARGETS ${PROJECT_NAME} ${MAYA_TARGET_TYPE} DESTINATION plug-ins) 29 | install(FILES "cvwrap.cl" DESTINATION plug-ins) 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Chad Vernon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cvwrap 2 | A Maya wrap deformer that is faster than Maya's wrap deformer, can be rebounded, has a GPU implementation, and supports inverted front of chain blend shapes. 3 | 4 | You can purchase a video series documenting the development of this plug-in from scratch at [CGCircuit](http://www.cgcircuit.com/course/creating-a-gpu-driven-wrap-deformer?affid=df9a2a33e2f653182abfd4cfc9b7159752671dde4280a13fc724d0c42b62d143c055f01d504ecc4d176537d5ce1994d0010d204a200f178091bf59c85380dbdc). 5 | 6 | ```python 7 | sphere = cmds.polySphere(sx=10, sy=10)[0] 8 | cube = cmds.polyCube(w=2.3, h=2.3, d=2.3, sx=5, sy=5, sz=5)[0] 9 | 10 | # Create a new wrap 11 | wrap_node = cmds.cvWrap(sphere, cube, name='wrapnode', radius=0.1) 12 | 13 | # Rebind a vertex 14 | cmds.select(['{0}.vtx[75]'.format(sphere)]) 15 | cmds.select(['{0}.{1}'.format(cube, faces) for faces in ['f[110:111]', 'f[115:116]']], add=True) 16 | cmds.cvWrap(rb=wrap_node) 17 | 18 | file_path = r'E:\My Documents\maya\projects\default\data\binding.wrap' 19 | # Export the binding 20 | cmds.cvWrap(wrap_node, ex=file_path) 21 | 22 | # Recreate the wrap node with the adjusted binding 23 | cmds.delete(wrap_node) 24 | wrap_node = cmds.cvWrap(sphere, cube, name=wrap_node, b=file_path) 25 | 26 | # Import the binding again 27 | cmds.cvWrap(wrap_node, im=file_path) 28 | ``` 29 | -------------------------------------------------------------------------------- /src/pluginMain.cpp: -------------------------------------------------------------------------------- 1 | #include "cvWrapDeformer.h" 2 | #include "cvWrapCmd.h" 3 | 4 | #include 5 | #include 6 | 7 | MStatus initializePlugin(MObject obj) { 8 | MStatus status; 9 | MFnPlugin plugin(obj, "Chad Vernon", "1.0", "Any"); 10 | status = plugin.registerNode(CVWrap::kName, CVWrap::id, CVWrap::creator, CVWrap::initialize, 11 | MPxNode::kDeformerNode); 12 | CHECK_MSTATUS_AND_RETURN_IT(status); 13 | status = plugin.registerCommand(CVWrapCmd::kName, CVWrapCmd::creator, CVWrapCmd::newSyntax); 14 | CHECK_MSTATUS_AND_RETURN_IT(status); 15 | #if MAYA_API_VERSION >= 201600 16 | status = MGPUDeformerRegistry::registerGPUDeformerCreator(CVWrap::kName, "cvWrapOverride", 17 | CVWrapGPU::GetGPUDeformerInfo()); 18 | CHECK_MSTATUS_AND_RETURN_IT(status); 19 | // Set the load path so we can find the cl kernel. 20 | CVWrapGPU::pluginLoadPath = plugin.loadPath(); 21 | #endif 22 | 23 | 24 | if (MGlobal::mayaState() == MGlobal::kInteractive) { 25 | MGlobal::executePythonCommandOnIdle("import cvwrap.menu"); 26 | MGlobal::executePythonCommandOnIdle("cvwrap.menu.create_menuitems()"); 27 | } 28 | 29 | return status; 30 | } 31 | 32 | MStatus uninitializePlugin( MObject obj) { 33 | MStatus status; 34 | MFnPlugin plugin(obj); 35 | 36 | #if MAYA_API_VERSION >= 201600 37 | status = MGPUDeformerRegistry::deregisterGPUDeformerCreator(CVWrap::kName, "cvWrapOverride"); 38 | CHECK_MSTATUS_AND_RETURN_IT(status); 39 | #endif 40 | status = plugin.deregisterCommand(CVWrapCmd::kName); 41 | CHECK_MSTATUS_AND_RETURN_IT(status); 42 | status = plugin.deregisterNode(CVWrap::id); 43 | CHECK_MSTATUS_AND_RETURN_IT(status); 44 | 45 | if (MGlobal::mayaState() == MGlobal::kInteractive) { 46 | MGlobal::executePythonCommandOnIdle("import cvwrap.menu"); 47 | MGlobal::executePythonCommandOnIdle("cvwrap.menu.destroy_menuitems()"); 48 | } 49 | 50 | return status; 51 | } 52 | -------------------------------------------------------------------------------- /src/bindingio.h: -------------------------------------------------------------------------------- 1 | #ifndef CVWRAP_BindingIO_H 2 | #define CVWRAP_BindingIO_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /** 10 | The BindingIO is used to import and export binding information from a wrap node. 11 | */ 12 | class BindingIO { 13 | public: 14 | /** 15 | Exports the binding information to disk. 16 | */ 17 | MStatus ExportBinding(std::ofstream& out, MObject& oWrapNode); 18 | 19 | /** 20 | Imports the binding information from disk. 21 | */ 22 | MStatus ImportBinding(std::ifstream& in, MObject& oWrapNode); 23 | 24 | const static float kWrapFileVersion; 25 | }; 26 | 27 | /** 28 | Convenience function to write a Maya array type to a binary stream. 29 | @param[in] out Output stream. 30 | @param[in] attribute A Maya array type. 31 | */ 32 | template 33 | void WriteAttribute(std::ofstream &out, const container& attribute) { 34 | unsigned int length = attribute.length(); 35 | out.write((char *)(&length), sizeof(length)); 36 | if (length > 0) { 37 | dataType * pAttr = new dataType[length]; 38 | attribute.get(pAttr); 39 | out.write((char *)pAttr, length * sizeof(dataType)); 40 | delete [] pAttr; 41 | } 42 | } 43 | 44 | /** 45 | Template specialization for MMatrix because they need to be read differently from 46 | normal array types. 47 | */ 48 | template <> 49 | void WriteAttribute(std::ofstream &out, const MMatrix& attribute); 50 | 51 | /** 52 | Convenience function to read a Maya array type from a binary stream. 53 | @param[in] in Input stream. 54 | @param[out] attribute A Maya array type. 55 | */ 56 | template 57 | void ReadAttribute(std::ifstream &in, container &attribute) { 58 | attribute.clear(); 59 | unsigned int length; 60 | in.read((char *)(&length), sizeof(length)); 61 | if (length > 0) { 62 | attribute.setLength(length); 63 | dataType* pValues = new dataType[length]; 64 | in.read((char *)pValues, length * sizeof(dataType)); 65 | for (unsigned int i = 0; i < length; i++) { 66 | attribute[i] = pValues[i]; 67 | } 68 | delete [] pValues; 69 | } 70 | } 71 | 72 | template <> 73 | void ReadAttribute(std::ifstream &in, MMatrix &matrix); 74 | 75 | 76 | #endif -------------------------------------------------------------------------------- /scripts/cvwrap/bindui.py: -------------------------------------------------------------------------------- 1 | import maya.cmds as cmds 2 | if cmds.about(api=True) >= 201700: 3 | from PySide2 import QtWidgets as QtGui 4 | else: 5 | from PySide import QtGui 6 | from functools import partial 7 | from maya.app.general.mayaMixin import MayaQWidgetBaseMixin 8 | 9 | _win = None 10 | def show(): 11 | global _win 12 | if _win == None: 13 | _win = BindingDialog() 14 | _win.show() 15 | 16 | 17 | class BindingDialog(MayaQWidgetBaseMixin, QtGui.QDialog): 18 | 19 | def __init__(self, parent=None): 20 | super(BindingDialog, self).__init__(parent) 21 | self.resize(600, 200) 22 | self.setWindowTitle('cvWrap Rebind') 23 | vbox = QtGui.QVBoxLayout(self) 24 | 25 | label_width = 130 26 | 27 | hbox = QtGui.QHBoxLayout() 28 | vbox.addLayout(hbox) 29 | label = QtGui.QLabel('Components to rebind:') 30 | label.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) 31 | label.setMinimumWidth(label_width) 32 | label.setMaximumWidth(label_width) 33 | hbox.addWidget(label) 34 | self.components_to_rebind = QtGui.QLineEdit() 35 | self.components_to_rebind.textChanged.connect(self.populate_cvwrap_dropdown) 36 | hbox.addWidget(self.components_to_rebind) 37 | button = QtGui.QPushButton('Set Components') 38 | button.released.connect(partial(self.set_selected_text, widget=self.components_to_rebind)) 39 | hbox.addWidget(button) 40 | 41 | hbox = QtGui.QHBoxLayout() 42 | vbox.addLayout(hbox) 43 | label = QtGui.QLabel('Faces to rebind to:') 44 | label.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) 45 | label.setMinimumWidth(label_width) 46 | label.setMaximumWidth(label_width) 47 | hbox.addWidget(label) 48 | self.target_faces = QtGui.QLineEdit() 49 | hbox.addWidget(self.target_faces) 50 | button = QtGui.QPushButton('Set Faces') 51 | button.released.connect(partial(self.set_selected_text, widget=self.target_faces)) 52 | hbox.addWidget(button) 53 | 54 | hbox = QtGui.QHBoxLayout() 55 | vbox.addLayout(hbox) 56 | label = QtGui.QLabel('cvWrap node:') 57 | label.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) 58 | label.setMinimumWidth(label_width) 59 | label.setMaximumWidth(label_width) 60 | hbox.addWidget(label) 61 | self.cvwrap_combo = QtGui.QComboBox() 62 | hbox.addWidget(self.cvwrap_combo) 63 | 64 | hbox = QtGui.QHBoxLayout() 65 | vbox.addLayout(hbox) 66 | label = QtGui.QLabel('Sample radius:') 67 | label.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) 68 | label.setMinimumWidth(label_width) 69 | label.setMaximumWidth(label_width) 70 | hbox.addWidget(label) 71 | self.sample_radius = QtGui.QDoubleSpinBox() 72 | self.sample_radius.setValue(0.1) 73 | self.sample_radius.setRange(0, 100) 74 | self.sample_radius.setDecimals(2) 75 | self.sample_radius.setSingleStep(.1) 76 | hbox.addWidget(self.sample_radius) 77 | 78 | vbox.addStretch() 79 | 80 | hbox = QtGui.QHBoxLayout() 81 | vbox.addLayout(hbox) 82 | button = QtGui.QPushButton('Rebind') 83 | button.released.connect(self.rebind) 84 | hbox.addWidget(button) 85 | 86 | def set_selected_text(self, widget): 87 | sel = cmds.ls(sl=True) 88 | text = ' '.join(sel) 89 | widget.setText(text) 90 | 91 | def populate_cvwrap_dropdown(self, text): 92 | node = text.split() 93 | if not node: 94 | return 95 | node = node[0].split('.') 96 | if not node: 97 | return 98 | node = node[0] 99 | wrap_nodes = [x for x in cmds.listHistory(node, pdo=True) or [] 100 | if cmds.nodeType(x) == 'cvWrap'] 101 | self.cvwrap_combo.clear() 102 | self.cvwrap_combo.addItems(wrap_nodes) 103 | 104 | def rebind(self): 105 | components = self.components_to_rebind.text().split() 106 | faces = self.target_faces.text().split() 107 | wrap_node = self.cvwrap_combo.currentText() 108 | radius = self.sample_radius.value() 109 | # Make sure the faces are actual faces. If they are not, convert to faces. 110 | cmds.select(faces) 111 | cmds.ConvertSelectionToFaces() 112 | faces = cmds.ls(sl=True) 113 | 114 | cmds.select(components) 115 | cmds.ConvertSelectionToVertices() 116 | cmds.select(faces, add=True) 117 | cmds.cvWrap(rb=wrap_node, radius=radius) 118 | print('Rebounded vertices') 119 | 120 | -------------------------------------------------------------------------------- /src/cvWrapDeformer.h: -------------------------------------------------------------------------------- 1 | #ifndef CVWRAPDEFORMER_H 2 | #define CVWRAPDEFORMER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #if MAYA_API_VERSION >= 201600 15 | #include 16 | #include 17 | #include 18 | #include 19 | #endif 20 | 21 | #include 22 | #include 23 | #include "common.h" 24 | 25 | struct TaskData { 26 | MMatrix drivenMatrix; 27 | MMatrix drivenInverseMatrix; 28 | float envelope; 29 | float scale; 30 | 31 | MIntArray membership; 32 | MFloatArray paintWeights; 33 | MPointArray points; 34 | 35 | MPointArray driverPoints; 36 | MFloatVectorArray driverNormals; 37 | MMatrixArray bindMatrices; 38 | std::vector sampleIds; 39 | std::vector sampleWeights; 40 | std::vector triangleVerts; 41 | std::vector baryCoords; 42 | }; 43 | 44 | 45 | class CVWrap : public MPxDeformerNode { 46 | public: 47 | CVWrap(); 48 | virtual ~CVWrap(); 49 | virtual void postConstructor(); 50 | virtual MStatus deform(MDataBlock& data, MItGeometry& iter, const MMatrix& mat, 51 | unsigned int mIndex); 52 | virtual MStatus setDependentsDirty(const MPlug& plugBeingDirtied, MPlugArray& affectedPlugs); 53 | 54 | static void* creator(); 55 | static MStatus initialize(); 56 | 57 | 58 | /** 59 | Distributes the ThreadData objects to the parallel threads. 60 | @param[in] data The user defined data. In this case, the ThreadData array. 61 | @param[in] pRoot Maya's root task. 62 | */ 63 | static void CreateTasks(void *data, MThreadRootTask *pRoot); 64 | static MThreadRetVal EvaluateWrap(void *pParam); 65 | 66 | const static char* kName; /**< The name of the node. */ 67 | static MObject aBindDriverGeo; 68 | static MObject aDriverGeo; 69 | static MObject aBindData; 70 | static MObject aSampleComponents; 71 | static MObject aSampleWeights; 72 | /** The vertex indices of the triangle containing the origin of each coordinate system. */ 73 | static MObject aTriangleVerts; 74 | /** The indices of the tangents used to calculate the up vector. */ 75 | static MObject aBarycentricWeights; 76 | 77 | static MObject aBindMatrix; 78 | static MObject aNumTasks; 79 | static MObject aScale; 80 | static MTypeId id; 81 | 82 | private: 83 | static void aboutToDeleteCB(MObject &node, MDGModifier &modifier, void *clientData); 84 | 85 | std::map dirty_; 86 | std::vector taskData_; /**< Per geometry evaluation data. */ 87 | std::vector*> threadData_; 88 | MCallbackId onDeleteCallbackId; 89 | }; 90 | 91 | 92 | 93 | #if MAYA_API_VERSION >= 201600 94 | // the GPU override implementation of the offsetNode 95 | // 96 | 97 | class CVWrapGPU : public MPxGPUDeformer { 98 | public: 99 | // Virtual methods from MPxGPUDeformer 100 | CVWrapGPU(); 101 | virtual ~CVWrapGPU(); 102 | 103 | #if MAYA_API_VERSION <= 201700 104 | virtual MPxGPUDeformer::DeformerStatus evaluate(MDataBlock& block, const MEvaluationNode&, 105 | const MPlug& plug, unsigned int numElements, 106 | const MAutoCLMem, const MAutoCLEvent, 107 | MAutoCLMem, MAutoCLEvent&); 108 | #else 109 | virtual MPxGPUDeformer::DeformerStatus evaluate(MDataBlock& block, const MEvaluationNode& evaluationNode, 110 | const MPlug& plug, const MGPUDeformerData& inputData, 111 | MGPUDeformerData& outputData); 112 | #endif 113 | virtual void terminate(); 114 | 115 | static MGPUDeformerRegistrationInfo* GetGPUDeformerInfo(); 116 | static bool ValidateNode(MDataBlock& block, const MEvaluationNode&, const MPlug& plug, MStringArray* messages); 117 | /**< The path of where the plug-in is loaded from. Used to find the cl kernel. */ 118 | static MString pluginLoadPath; 119 | 120 | private: 121 | // helper methods 122 | MStatus EnqueueBindData(MDataBlock& data, const MEvaluationNode& evaluationNode, const MPlug& plug); 123 | MStatus EnqueueDriverData(MDataBlock& data, const MEvaluationNode& evaluationNode, const MPlug& plug); 124 | MStatus EnqueuePaintMapData(MDataBlock& data, const MEvaluationNode& evaluationNode, unsigned int numElements, const MPlug& plug); 125 | 126 | // Storage for data on the GPU 127 | MAutoCLMem driverPoints_; 128 | MAutoCLMem driverNormals_; 129 | MAutoCLMem paintWeights_; 130 | MAutoCLMem bindMatrices_; 131 | MAutoCLMem sampleCounts_; 132 | MAutoCLMem sampleOffsets_; 133 | MAutoCLMem sampleIds_; 134 | MAutoCLMem sampleWeights_; 135 | MAutoCLMem triangleVerts_; 136 | MAutoCLMem baryCoords_; 137 | MAutoCLMem drivenMatrices_; 138 | 139 | unsigned int numElements_; 140 | 141 | // Kernel 142 | MAutoCLKernel kernel_; 143 | }; 144 | 145 | 146 | /** 147 | The 148 | */ 149 | class CVWrapGPUDeformerInfo : public MGPUDeformerRegistrationInfo { 150 | public: 151 | CVWrapGPUDeformerInfo(){} 152 | virtual ~CVWrapGPUDeformerInfo(){} 153 | 154 | virtual MPxGPUDeformer* createGPUDeformer() { 155 | return new CVWrapGPU(); 156 | } 157 | 158 | 159 | 160 | #if MAYA_API_VERSION >= 201650 161 | virtual bool validateNodeInGraph(MDataBlock& block, const MEvaluationNode& evaluationNode, 162 | const MPlug& plug, MStringArray* messages) { 163 | return true; 164 | } 165 | 166 | virtual bool validateNodeValues(MDataBlock& block, const MEvaluationNode& evaluationNode, 167 | const MPlug& plug, MStringArray* messages) { 168 | return true; 169 | } 170 | #else 171 | virtual bool validateNode(MDataBlock& block, const MEvaluationNode& evaluationNode, 172 | const MPlug& plug, MStringArray* messages) { 173 | return true; 174 | } 175 | #endif 176 | }; 177 | 178 | #endif // End Maya 2016 179 | 180 | #endif 181 | -------------------------------------------------------------------------------- /src/cvWrapCmd.h: -------------------------------------------------------------------------------- 1 | #ifndef CVWRAPCMD_H 2 | #define CVWRAPCMD_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "common.h" 30 | 31 | struct BindData { 32 | MPointArray inputPoints; /**< The world space points of the geometry to be wrapped. */ 33 | MPointArray driverPoints; /**< The world space points of the driver geometry. */ 34 | MFloatVectorArray driverNormals; /**< The world space normals of the driver geometry. */ 35 | std::vector perFaceVertices; /**< The per-face vertex ids of the driver. */ 36 | std::vector > perFaceTriangleVertices; /**< The per-face per-triangle vertex ids of the driver. */ 37 | MMeshIntersector intersector; /**< Closest point intersector on the driver mesh. */ 38 | MMeshIntersector subsetIntersector; /**< Closest point intersector on a subset mesh if we are rebinding. */ 39 | std::vector > adjacency; /**< Driver adjacency for surface crawling. */ 40 | MMatrix driverMatrix; /**< Driver matrix to convert closest points into world space. */ 41 | double radius; /**< Max crawl sample radius. */ 42 | 43 | /** 44 | Elements calculated in the threads. 45 | */ 46 | std::vector sampleIds; 47 | std::vector weights; 48 | MMatrixArray bindMatrices; 49 | std::vector coords; 50 | std::vector triangleVertices; 51 | }; 52 | 53 | 54 | /** 55 | The cvWrap command is used to create new cvWrap deformers and to import and export 56 | wrap bindings. 57 | */ 58 | class CVWrapCmd : public MPxCommand { 59 | public: 60 | enum CommandMode { kCommandCreate, kCommandExport, kCommandImport, kCommandHelp, kCommandRebind }; 61 | CVWrapCmd(); 62 | virtual MStatus doIt(const MArgList&); 63 | virtual MStatus undoIt(); 64 | virtual MStatus redoIt(); 65 | virtual bool isUndoable() const; 66 | static void* creator(); 67 | static MSyntax newSyntax(); 68 | 69 | /** 70 | Distributes the ThreadData objects to the parallel threads. 71 | @param[in] data The user defined data. In this case, the ThreadData array. 72 | @param[in] pRoot Maya's root task. 73 | */ 74 | static void CreateTasks(void *data, MThreadRootTask *pRoot); 75 | static MThreadRetVal CalculateBindingTask(void *pParam); 76 | 77 | const static char* kName; /**< The name of the command. */ 78 | 79 | /** 80 | Specifies the name of the cvWrap node. 81 | */ 82 | const static char* kNameFlagShort; 83 | const static char* kNameFlagLong; 84 | 85 | /** 86 | Specifies the sample radius of the binding. 87 | */ 88 | const static char* kRadiusFlagShort; 89 | const static char* kRadiusFlagLong; 90 | 91 | /** 92 | Specifies that a new bind mesh should be created. The bind mesh is only used for rebinding 93 | vertices and can be deleted at any time. Sometimes, artists may want to wrap different 94 | geometry with the same mesh. By default the command will reuse the same bind mesh for a driver, 95 | but if new geometry is being wrapped at a different pose, a new bind mesh should be created 96 | in order to correctly rebind. 97 | */ 98 | const static char* kNewBindMeshFlagShort; 99 | const static char* kNewBindMeshFlagLong; 100 | 101 | /** 102 | Export file path. 103 | */ 104 | const static char* kExportFlagShort; 105 | const static char* kExportFlagLong; 106 | 107 | /** 108 | Import file path. 109 | */ 110 | const static char* kImportFlagShort; 111 | const static char* kImportFlagLong; 112 | 113 | /** 114 | Path of a binding on disk rather than calculating binding from scratch. 115 | */ 116 | const static char* kBindingFlagShort; 117 | const static char* kBindingFlagLong; 118 | 119 | /** 120 | Specifies that the user wants to rebind the select vertices. 121 | */ 122 | const static char* kRebindFlagShort; 123 | const static char* kRebindFlagLong; 124 | 125 | /** 126 | Displays help. 127 | */ 128 | const static char* kHelpFlagShort; 129 | const static char* kHelpFlagLong; 130 | 131 | private: 132 | /** 133 | Gathers all the command arguments and sets necessary command states. 134 | @param[in] args Maya MArgList. 135 | */ 136 | MStatus GatherCommandArguments(const MArgList& args); 137 | 138 | /** 139 | Acquires the driver and driven dag paths from the input selection list. 140 | */ 141 | MStatus GetGeometryPaths(); 142 | 143 | /** 144 | Creates a new wrap deformer. 145 | */ 146 | MStatus CreateWrapDeformer(); 147 | 148 | /** 149 | Gets the latest cvWrap node in the history of the deformed shape. 150 | */ 151 | MStatus GetLatestWrapNode(); 152 | 153 | /** 154 | Create a new bind mesh and connect it to the wrap node. 155 | The bind mesh the mesh at the time of binding and is used to calculate binding information. 156 | */ 157 | MStatus CreateBindMesh(MDagPath& pathBindMesh); 158 | 159 | /** 160 | Connects the bind mesh message attribute to the wrap deformer. 161 | */ 162 | MStatus ConnectBindMesh(MDagPath& pathBindMesh); 163 | 164 | 165 | /** 166 | Calculates the binding data for the wrap deformer to work. 167 | @param[in] pathBindMesh The path to the mesh to bind to. 168 | @param[in] bindData The structure containing all the bind information. 169 | @param[in,out] dgMod The modifier to hold all the plug operations. 170 | */ 171 | MStatus CalculateBinding(MDagPath& pathBindMesh, BindData& bindData, MDGModifier& dgMod); 172 | 173 | /** 174 | Gets the MDagPath of any existing bind wrap mesh so we don't have to duplicate it for each 175 | new wrap. 176 | @param[out] pathBindMesh Storage for path to an existing bind mesh 177 | */ 178 | MStatus GetExistingBindMesh(MDagPath &pathBindMesh); 179 | 180 | /** 181 | Calculates new binding data for the selected components. 182 | */ 183 | MStatus Rebind(); 184 | 185 | /** 186 | Get the bind mesh connected to the wrap node. 187 | @param[in] oWrapNode MObject to a cvWrap node.. 188 | @param[out] pathBindMesh The path to the bind mesh. 189 | */ 190 | MStatus GetBindMesh(MObject& oWrapNode, MDagPath& pathBindMesh); 191 | 192 | 193 | /** 194 | Creates the mesh with the subset of faces used to calculate the rebind. 195 | @param[out] pathDriverSubset Path the new driver subset mesh. 196 | */ 197 | MStatus CreateRebindSubsetMesh(MDagPath& pathDriverSubset); 198 | 199 | 200 | MString name_; /**< Name of cvWrap node to create. */ 201 | double radius_; /**< Binding sample radius. */ 202 | CommandMode command_; 203 | MString filePath_; 204 | bool useBinding_; 205 | bool newBindMesh_; 206 | MSelectionList selectionList_; /**< Selected command input nodes. */ 207 | MObject oWrapNode_; /**< MObject to the cvWrap node in focus. */ 208 | MDagPath pathDriver_; /**< Path to the shape wrapping the other shape. */ 209 | MObject driverComponents_; /**< Selected driver components used for rebinding. */ 210 | MDagPathArray pathDriven_; /**< Paths to the shapes being wrapped. */ 211 | MObjectArray drivenComponents_; /**< Selected driven components used for rebinding. */ 212 | MDGModifier dgMod_; 213 | MStringArray bindMeshes_; 214 | 215 | 216 | }; 217 | 218 | #endif 219 | -------------------------------------------------------------------------------- /src/bindingio.cpp: -------------------------------------------------------------------------------- 1 | #include "bindingio.h" 2 | #include "cvWrapDeformer.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | const float BindingIO::kWrapFileVersion = 1.0f; 12 | 13 | template <> 14 | void WriteAttribute(std::ofstream &out, const MMatrix& attribute) { 15 | double values[16]; 16 | for (int i = 0; i < 4; i++) { 17 | for (int j = 0; j < 4; j++) { 18 | values[i*4 + j] = attribute[i][j]; 19 | } 20 | } 21 | out.write((char *)values, 16 * sizeof(double)); 22 | } 23 | 24 | template <> 25 | void ReadAttribute(std::ifstream &in, MMatrix &matrix) { 26 | double values[16]; 27 | in.read((char *)values, 16 * sizeof(double)); 28 | for (int i = 0; i < 4; i++) { 29 | for (int j = 0; j < 4; j++) { 30 | matrix[i][j] = values[(i * 4) + j]; 31 | } 32 | } 33 | } 34 | 35 | MStatus BindingIO::ExportBinding(std::ofstream& out, MObject& oWrapNode) { 36 | MStatus status; 37 | MFnWeightGeometryFilter fnWrapNode(oWrapNode, &status); 38 | CHECK_MSTATUS_AND_RETURN_IT(status); 39 | 40 | if (fnWrapNode.typeId() != CVWrap::id) { 41 | MGlobal::displayError(fnWrapNode.name() + " is not a cvWrap node."); 42 | CHECK_MSTATUS_AND_RETURN_IT(MS::kFailure); 43 | } 44 | 45 | out.write((char *)&kWrapFileVersion, sizeof(float)); 46 | 47 | MPlug plugBindData = fnWrapNode.findPlug(CVWrap::aBindData, false, &status); 48 | CHECK_MSTATUS_AND_RETURN_IT(status); 49 | // Get the input geometry so we can get the geometry indices 50 | MObjectArray outputGeometry; 51 | status = fnWrapNode.getOutputGeometry(outputGeometry); 52 | CHECK_MSTATUS_AND_RETURN_IT(status); 53 | // Write the number of geometry 54 | unsigned int geometryCount = outputGeometry.length(); 55 | out.write((char *)(&geometryCount), sizeof(geometryCount)); 56 | 57 | MIntArray triangleVerts(3); /**< Storage for the triangle vertex ids. */ 58 | MFloatArray baryCoords(3); /**< Storage for the barycentric weights. */ 59 | for (unsigned int i = 0; i < outputGeometry.length(); ++i) { 60 | unsigned int geomIndex = fnWrapNode.indexForOutputShape(outputGeometry[i], &status); 61 | // Get the plugs to the binding attributes for this geometry 62 | MPlug plugBind = plugBindData.elementByLogicalIndex(geomIndex, &status); 63 | CHECK_MSTATUS_AND_RETURN_IT(status); 64 | MPlug plugSampleWeights = plugBind.child(CVWrap::aSampleWeights, &status); 65 | CHECK_MSTATUS_AND_RETURN_IT(status); 66 | MPlug plugSampleVerts = plugBind.child(CVWrap::aSampleComponents, &status); 67 | CHECK_MSTATUS_AND_RETURN_IT(status); 68 | MPlug plugSampleBindMatrix = plugBind.child(CVWrap::aBindMatrix, &status); 69 | CHECK_MSTATUS_AND_RETURN_IT(status); 70 | MPlug plugTriangleVerts = plugBind.child(CVWrap::aTriangleVerts, &status); 71 | CHECK_MSTATUS_AND_RETURN_IT(status); 72 | MPlug plugBarycentricWeights = plugBind.child(CVWrap::aBarycentricWeights, &status); 73 | CHECK_MSTATUS_AND_RETURN_IT(status); 74 | 75 | unsigned int numElements = plugSampleWeights.numElements(); 76 | out.write((char *)(&numElements), sizeof(numElements)); 77 | 78 | for (unsigned int j = 0; j < numElements; ++j) { 79 | // Write the logical index 80 | MPlug plugSampleVertElement = plugSampleVerts.elementByPhysicalIndex(j, &status); 81 | CHECK_MSTATUS_AND_RETURN_IT(status); 82 | unsigned int logicalIndex = plugSampleVertElement.logicalIndex(); 83 | out.write((char *)(&logicalIndex), sizeof(logicalIndex)); 84 | 85 | // Export sample vertex ids 86 | MObject oSampleIds = plugSampleVertElement.asMObject(); 87 | CHECK_MSTATUS_AND_RETURN_IT(status); 88 | MFnIntArrayData fnIntData(oSampleIds, &status); 89 | CHECK_MSTATUS_AND_RETURN_IT(status); 90 | MIntArray sampleIds = fnIntData.array(); 91 | WriteAttribute(out, sampleIds); 92 | 93 | // Export sample weights 94 | MObject oWeightData = plugSampleWeights.elementByPhysicalIndex(j, &status).asMObject(); 95 | CHECK_MSTATUS_AND_RETURN_IT(status); 96 | MFnDoubleArrayData fnDoubleData(oWeightData); 97 | MDoubleArray weights = fnDoubleData.array(); 98 | WriteAttribute(out, weights); 99 | 100 | // Export bind matrix 101 | MObject oBindMatrix = plugSampleBindMatrix.elementByPhysicalIndex(j, &status).asMObject(); 102 | CHECK_MSTATUS_AND_RETURN_IT(status); 103 | MFnMatrixData fnMatrixData(oBindMatrix, &status); 104 | CHECK_MSTATUS_AND_RETURN_IT(status); 105 | WriteAttribute(out, fnMatrixData.matrix()); 106 | 107 | // Export triangle vertices 108 | MObject oTriangleVerts = plugTriangleVerts.elementByPhysicalIndex(j, &status).asMObject(); 109 | CHECK_MSTATUS_AND_RETURN_IT(status); 110 | MFnNumericData fnNumericData(oTriangleVerts, &status); 111 | CHECK_MSTATUS_AND_RETURN_IT(status); 112 | fnNumericData.getData3Int(triangleVerts[0], triangleVerts[1], triangleVerts[2]); 113 | WriteAttribute(out, triangleVerts); 114 | 115 | // Export the barycentric weights 116 | MObject oBaryWeights = plugBarycentricWeights.elementByPhysicalIndex(j, &status).asMObject(); 117 | CHECK_MSTATUS_AND_RETURN_IT(status); 118 | MFnNumericData fnBaryData(oBaryWeights, &status); 119 | CHECK_MSTATUS_AND_RETURN_IT(status); 120 | fnBaryData.getData3Float(baryCoords[0], baryCoords[1], baryCoords[2]); 121 | WriteAttribute(out, baryCoords); 122 | } 123 | } 124 | 125 | MGlobal::displayInfo("Wrap binding exported."); 126 | 127 | return status; 128 | } 129 | 130 | 131 | 132 | MStatus BindingIO::ImportBinding(std::ifstream& in, MObject& oWrapNode) { 133 | MStatus status; 134 | 135 | MFnWeightGeometryFilter fnWrapNode(oWrapNode, &status); 136 | CHECK_MSTATUS_AND_RETURN_IT(status); 137 | MPlug plugBindData = fnWrapNode.findPlug(CVWrap::aBindData, false, &status); 138 | CHECK_MSTATUS_AND_RETURN_IT(status); 139 | 140 | float version; 141 | in.read((char *)(&version), sizeof(float)); 142 | 143 | unsigned int geometryCount = 0; 144 | in.read((char *)(&geometryCount), sizeof(geometryCount)); 145 | 146 | MFnMatrixData fnMatrixData; 147 | MFnIntArrayData fnIntData; 148 | MFnDoubleArrayData fnDoubleData; 149 | MFnNumericData fnNumericData; 150 | // We are assuming that the geometryIndices are compact and continuous. It is possible 151 | // that the indices could be sparse, but we will ignore that corner case. 152 | for (unsigned int geomIndex = 0; geomIndex < geometryCount; ++geomIndex) { 153 | // Get the plugs to the binding attributes for this geometry 154 | MPlug plugBind = plugBindData.elementByLogicalIndex(geomIndex, &status); 155 | CHECK_MSTATUS_AND_RETURN_IT(status); 156 | MPlug plugSampleWeights = plugBind.child(CVWrap::aSampleWeights, &status); 157 | CHECK_MSTATUS_AND_RETURN_IT(status); 158 | MPlug plugSampleVerts = plugBind.child(CVWrap::aSampleComponents, &status); 159 | CHECK_MSTATUS_AND_RETURN_IT(status); 160 | MPlug plugSampleBindMatrix = plugBind.child(CVWrap::aBindMatrix, &status); 161 | CHECK_MSTATUS_AND_RETURN_IT(status); 162 | MPlug plugTriangleVerts = plugBind.child(CVWrap::aTriangleVerts, &status); 163 | CHECK_MSTATUS_AND_RETURN_IT(status); 164 | MPlug plugBarycentricWeights = plugBind.child(CVWrap::aBarycentricWeights, &status); 165 | CHECK_MSTATUS_AND_RETURN_IT(status); 166 | 167 | unsigned int numElements = plugSampleWeights.numElements(); 168 | in.read((char *)(&numElements), sizeof(numElements)); 169 | for (unsigned int i = 0; i < numElements; ++i) { 170 | unsigned int logicalIndex = 0; 171 | in.read((char *)(&logicalIndex), sizeof(logicalIndex)); 172 | 173 | // Sample vert ids. 174 | MIntArray sampleIds; 175 | ReadAttribute(in, sampleIds); 176 | MObject oIntData = fnIntData.create(sampleIds, &status); 177 | CHECK_MSTATUS_AND_RETURN_IT(status); 178 | plugSampleVerts.elementByLogicalIndex(logicalIndex, &status).setMObject(oIntData); 179 | CHECK_MSTATUS_AND_RETURN_IT(status); 180 | 181 | // Sample weights 182 | MDoubleArray weights; 183 | ReadAttribute(in, weights); 184 | MObject oDoubleData = fnDoubleData.create(weights, &status); 185 | CHECK_MSTATUS_AND_RETURN_IT(status); 186 | plugSampleWeights.elementByLogicalIndex(logicalIndex, &status).setMObject(oDoubleData); 187 | CHECK_MSTATUS_AND_RETURN_IT(status); 188 | 189 | // Bind matrix 190 | MMatrix bindMatrix; 191 | ReadAttribute(in, bindMatrix); 192 | MObject oMatrixData = fnMatrixData.create(bindMatrix, &status); 193 | CHECK_MSTATUS_AND_RETURN_IT(status); 194 | plugSampleBindMatrix.elementByLogicalIndex(logicalIndex, &status).setMObject(oMatrixData); 195 | CHECK_MSTATUS_AND_RETURN_IT(status); 196 | 197 | // Triangle vertices 198 | MIntArray triangleVertices; 199 | ReadAttribute(in, triangleVertices); 200 | MObject oNumericData = fnNumericData.create(MFnNumericData::k3Int, &status); 201 | CHECK_MSTATUS_AND_RETURN_IT(status); 202 | status = fnNumericData.setData3Int(triangleVertices[0], triangleVertices[1], 203 | triangleVertices[2]); 204 | CHECK_MSTATUS_AND_RETURN_IT(status); 205 | plugTriangleVerts.elementByLogicalIndex(logicalIndex, &status).setMObject(oNumericData); 206 | CHECK_MSTATUS_AND_RETURN_IT(status); 207 | 208 | // Barycentric coordinates 209 | MFloatArray coords; 210 | ReadAttribute(in, coords); 211 | oNumericData = fnNumericData.create(MFnNumericData::k3Float, &status); 212 | CHECK_MSTATUS_AND_RETURN_IT(status); 213 | status = fnNumericData.setData3Float(coords[0], coords[1], coords[2]); 214 | CHECK_MSTATUS_AND_RETURN_IT(status); 215 | plugBarycentricWeights.elementByLogicalIndex(logicalIndex, &status).setMObject(oNumericData); 216 | CHECK_MSTATUS_AND_RETURN_IT(status); 217 | } 218 | } 219 | MGlobal::displayInfo("Wrap binding imported."); 220 | 221 | return MS::kSuccess; 222 | } 223 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | /** 2 | Contains various helper functions. 3 | */ 4 | 5 | #ifndef CVWRAP_COMMON_H 6 | #define CVWRAP_COMMON_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #ifdef __AVX__ 21 | #include 22 | #include 23 | #endif 24 | 25 | /** 26 | Helper function to start a new progress bar. 27 | @param[in] title Status title. 28 | @param[in] count Progress bar maximum count. 29 | */ 30 | void StartProgress(const MString& title, unsigned int count); 31 | 32 | 33 | /** 34 | Helper function to increase the progress bar by the specified amount. 35 | @param[in] step Step amount. 36 | */ 37 | void StepProgress(int step); 38 | 39 | 40 | /** 41 | Check if the progress has been cancelled. 42 | @return true if the progress has been cancelled. 43 | */ 44 | bool ProgressCancelled(); 45 | 46 | 47 | /** 48 | Ends any running progress bar. 49 | */ 50 | void EndProgress(); 51 | 52 | 53 | /** 54 | Checks if the path points to a shape node. 55 | @param[in] path A dag path. 56 | @return true if the path points to a shape node. 57 | */ 58 | bool IsShapeNode(MDagPath& path); 59 | 60 | 61 | /** 62 | Ensures that the given dag path points to a non-intermediate shape node. 63 | @param[in,out] path Path to a dag node that could be a transform or a shape. 64 | On return, the path will be to a shape node if one exists. 65 | @param[in] intermediate true to get the intermediate shape. 66 | @return MStatus. 67 | */ 68 | MStatus GetShapeNode(MDagPath& path, bool intermediate=false); 69 | 70 | 71 | /** 72 | Get the MDagPath of an object. 73 | @param[in] name Name of a dag node. 74 | @param[out] path Storage for the dag path. 75 | */ 76 | MStatus GetDagPath(MString& name, MDagPath& path); 77 | 78 | 79 | /** 80 | Delete all intermediate shapes of the given dag path. 81 | @param[in] path MDagPath. 82 | */ 83 | MStatus DeleteIntermediateObjects(MDagPath& path); 84 | 85 | 86 | /** 87 | Helper struct to hold the 3 barycentric coordinates. 88 | */ 89 | struct BaryCoords { 90 | float coords[3]; 91 | float operator[](int index) const { return coords[index]; } 92 | float& operator[](int index) { return coords[index]; } 93 | }; 94 | 95 | 96 | /** 97 | Get the barycentric coordinates of point P in the triangle specified by points A,B,C. 98 | @param[in] P The sample point. 99 | @param[in] A Triangle point. 100 | @param[in] B Triangle point. 101 | @param[in] C Triangle point. 102 | @param[out] coords Barycentric coordinates storage. 103 | */ 104 | void GetBarycentricCoordinates(const MPoint& P, const MPoint& A, const MPoint& B, const MPoint& C, 105 | BaryCoords& coords); 106 | 107 | 108 | /** 109 | Get the vertex adjacency of the specified mesh. The vertex adjacency are the vertex ids 110 | of the connected faces of each vertex. 111 | @param[in] pathMesh Path to a mesh. 112 | @param[out] adjacency Ajdancency storage of the adjancency per vertex id. 113 | */ 114 | MStatus GetAdjacency(MDagPath& pathMesh, std::vector >& adjacency); 115 | 116 | 117 | /** 118 | Crawls the surface to find all the points within the sampleradius. 119 | @param[in] startPoint The position from which to start the crawl. 120 | @param[in] vertexIndices The starting vertex indices we want to crawl to. 121 | @param[in] points The array of all the mesh points. 122 | @param[in] maxDistance The maximum crawl distance. 123 | @param[in] adjacency Vertex adjacency data from the GetAdjacency function. 124 | @param[out] distances Storage for the distances to the crawled points. 125 | @return MStatus 126 | */ 127 | MStatus CrawlSurface(const MPoint& startPoint, const MIntArray& vertexIndices, MPointArray& points, double maxDistance, 128 | std::vector >& adjacency, std::map& distances); 129 | 130 | 131 | /** 132 | Calculates a weight for each vertex within the crawl sample radius. Vertices that are further 133 | away from the origin should have a lesser effect than vertices closer to the origin. 134 | @param[in] distances Crawl distances calculated from CrawlSurface. 135 | @param[in] radius Sample radius. 136 | @param[out] vertexIds Storage for the vertex ids sampled during the crawl. 137 | @param[out] weights Storage for the calculated weights of each sampled vertex. 138 | */ 139 | void CalculateSampleWeights(const std::map& distances, double radius, 140 | MIntArray& vertexIds, MDoubleArray& weights); 141 | 142 | /** 143 | Creates an orthonormal basis using the given point and two axes. 144 | @param[in] origin Position. 145 | @param[in] normal Normal vector. 146 | @param[in] up Up vector. 147 | @param[out] matrix Generated matrix. 148 | */ 149 | void CreateMatrix(const MPoint& origin, const MVector& normal, const MVector& up, 150 | MMatrix& matrix); 151 | 152 | /** 153 | Calculates the components necessary to create a wrap basis matrix. 154 | @param[in] weights The sample weights array from the wrap binding. 155 | @param[in] coords The barycentric coordinates of the closest point. 156 | @param[in] triangleVertices The vertex ids forming the triangle of the closest point. 157 | @param[in] points The driver point array. 158 | @param[in] normals The driver per-vertex normal array. 159 | @param[in] sampleIds The vertex ids on the driver of the current sample. 160 | @param[in] alignedStorage double array that is 32 byte aligned for AVX. 161 | @param[out] origin The origin of the coordinate system. 162 | @param[out] up The up vector of the coordinate system. 163 | @param[out] normal The normal vector of the coordinate system. 164 | */ 165 | void CalculateBasisComponents(const MDoubleArray& weights, const BaryCoords& coords, 166 | const MIntArray& triangleVertices, const MPointArray& points, 167 | const MFloatVectorArray& normals, const MIntArray& sampleIds, 168 | double* alignedStorage, 169 | MPoint& origin, MVector& up, MVector& normal); 170 | 171 | /** 172 | Ensures that the up and normal vectors are perpendicular to each other. 173 | @param[in] weights The sample weights array from the wrap binding. 174 | @param[in] points The driver point array. 175 | @param[in] sampleIds The vertex ids on the driver of the current sample. 176 | @param[in] origin The origin of the coordinate system. 177 | @param[in] up The up vector of the coordinate system. 178 | @param[out] normal The normal vector of the coordinate system. 179 | */ 180 | void GetValidUp(const MDoubleArray& weights, const MPointArray& points, 181 | const MIntArray& sampleIds, const MPoint& origin, const MVector& normal, 182 | MVector& up); 183 | 184 | 185 | template 186 | struct ThreadData { 187 | unsigned int start; 188 | unsigned int end; 189 | unsigned int numTasks; 190 | double* alignedStorage; 191 | T* pData; 192 | 193 | #ifdef __AVX__ 194 | ThreadData() { 195 | alignedStorage = (double*) _mm_malloc(4*sizeof(double), 256); 196 | } 197 | ~ThreadData() { 198 | _mm_free(alignedStorage); 199 | } 200 | #endif 201 | }; 202 | 203 | 204 | /** 205 | Creates the data stuctures that will be sent to each thread. Divides the vertices into 206 | discrete chunks to be evaluated in the threads. 207 | @param[in] taskCount The number of individual tasks we want to divide the calculation into. 208 | @param[in] elementCount The number of vertices or elements to be divided up. 209 | @param[in] taskData The TaskData or BindData object. 210 | @param[out] threadData The array of ThreadData objects. It is assumed the array is of size taskCount. 211 | */ 212 | template 213 | void CreateThreadData(int taskCount, unsigned int elementCount, T* taskData, ThreadData* threadData) { 214 | unsigned int taskLength = (elementCount + taskCount - 1) / taskCount; 215 | unsigned int start = 0; 216 | unsigned int end = taskLength; 217 | int lastTask = taskCount - 1; 218 | for(int i = 0; i < taskCount; i++) { 219 | if (i == lastTask) { 220 | end = elementCount; 221 | } 222 | threadData[i].start = start; 223 | threadData[i].end = end; 224 | threadData[i].numTasks = taskCount; 225 | threadData[i].pData = taskData; 226 | 227 | start += taskLength; 228 | end += taskLength; 229 | } 230 | } 231 | 232 | #ifdef __AVX__ 233 | /** 234 | Calculates 4 dot products at once. 235 | @param[in] w1 Weight vector x element. 236 | @param[in] w2 Weight vector y element. 237 | @param[in] w3 Weight vector z element. 238 | @param[in] w4 Weight vector w element. 239 | @param[in] p1 First vector. 240 | @param[in] p2 Second vector. 241 | @param[in] p3 Third vector. 242 | @param[in] p4 Fourth vector. 243 | @return A __m256d vector where each element is the corresponding p vector dot product with w. 244 | */ 245 | template 246 | __m256d Dot4(double w1, double w2, double w3, double w4, 247 | const T& p1, const T& p2, const T& p3, const T& p4) { 248 | __m256d xxx = _mm256_set_pd(p1.x, p2.x, p3.x, p4.x); 249 | __m256d yyy = _mm256_set_pd(p1.y, p2.y, p3.y, p4.y); 250 | __m256d zzz = _mm256_set_pd(p1.z, p2.z, p3.z, p4.z); 251 | __m256d www = _mm256_set_pd(w1, w2, w3, w4); 252 | __m256d xw = _mm256_mul_pd(xxx, www); 253 | __m256d yw = _mm256_mul_pd(yyy, www); 254 | __m256d zw = _mm256_mul_pd(zzz, www); 255 | __m256d ww = _mm256_mul_pd(www, www); // Dummy 256 | // low to high: xw0+xw1 yw0+yw1 xw2+xw3 yw2+yw3 257 | __m256d temp01 = _mm256_hadd_pd(xw, yw); 258 | // low to high: zw0+zw1 ww0+ww1 zw2+zw3 ww2+ww3 259 | __m256d temp23 = _mm256_hadd_pd(zw, ww); 260 | // low to high: xw2+xw3 yw2+yw3 zw0+zw1 ww0+ww1 261 | __m256d swapped = _mm256_permute2f128_pd(temp01, temp23, 0x21); 262 | // low to high: xw0+xw1 yw0+yw1 zw2+zw3 ww2+ww3 263 | __m256d blended = _mm256_blend_pd(temp01, temp23, 0xC); 264 | // low to high: xw0+xw1+xw2+xw3 yw0+yw1+yw2+yw3 zw0+zw1+zw2+zw3 ww0+ww1+ww2+ww3 265 | __m256d dotproduct = _mm256_add_pd(swapped, blended); 266 | return dotproduct; 267 | } 268 | #endif 269 | 270 | #endif 271 | -------------------------------------------------------------------------------- /src/cvwrap_pre2018.cl: -------------------------------------------------------------------------------- 1 | /* 2 | cvwrap kernel 3 | */ 4 | 5 | __kernel void cvwrap(__global float* finalPos, 6 | __global const float* initialPos, 7 | __global const float* driverPoints, 8 | __global const float* driverNormals, 9 | __global const float* paintWeights, 10 | __global const int* sampleCounts, 11 | __global const int* sampleOffsets, 12 | __global const int* sampleIds, 13 | __global const float* sampleWeights, 14 | __global const int* triangleVerts, 15 | __global const float* baryCoords, 16 | __global const float4* bindMatrices, 17 | __global const float4* drivenMatrices, 18 | const float envelope, 19 | const uint positionCount) { 20 | unsigned int positionId = get_global_id(0); 21 | if (positionId >= positionCount) { 22 | return; 23 | } 24 | unsigned int positionOffset = positionId * 3; 25 | 26 | // Start with the recreated point and normal using the barycentric coordinates of the hit point. 27 | /* 28 | Equivalent CPU code: 29 | ==================== 30 | MVector hitNormal; 31 | for (int i = 0; i < 3; ++i) { 32 | origin += points[triangleVertices[i]] * coords[i]; 33 | hitNormal += MVector(normals[triangleVertices[i]]) * coords[i]; 34 | } 35 | */ 36 | float baryA = baryCoords[positionOffset]; 37 | float baryB = baryCoords[positionOffset+1]; 38 | float baryC = baryCoords[positionOffset+2]; 39 | int triVertA = triangleVerts[positionOffset] * 3; 40 | int triVertB = triangleVerts[positionOffset+1] * 3; 41 | int triVertC = triangleVerts[positionOffset+2] * 3; 42 | float originX = driverPoints[triVertA] * baryA + 43 | driverPoints[triVertB] * baryB + 44 | driverPoints[triVertC] * baryC; 45 | float originY = driverPoints[triVertA+1] * baryA + 46 | driverPoints[triVertB+1] * baryB + 47 | driverPoints[triVertC+1] * baryC; 48 | float originZ = driverPoints[triVertA+2] * baryA + 49 | driverPoints[triVertB+2] * baryB + 50 | driverPoints[triVertC+2] * baryC; 51 | float hitNormalX = driverNormals[triVertA] * baryA + 52 | driverNormals[triVertB] * baryB + 53 | driverNormals[triVertC] * baryC; 54 | float hitNormalY = driverNormals[triVertA+1] * baryA + 55 | driverNormals[triVertB+1] * baryB + 56 | driverNormals[triVertC+1] * baryC; 57 | float hitNormalZ = driverNormals[triVertA+2] * baryA + 58 | driverNormals[triVertB+2] * baryB + 59 | driverNormals[triVertC+2] * baryC; 60 | 61 | /* 62 | Equivalent CPU code: 63 | ==================== 64 | unsigned int hitIndex = weights.length()-1; 65 | normal = hitNormal * weights[hitIndex]; 66 | */ 67 | int offset = sampleOffsets[positionId]; 68 | int hitIndex = offset + sampleCounts[positionId] - 1; 69 | float hitWeight = sampleWeights[hitIndex]; 70 | float normalX = hitNormalX * hitWeight; 71 | float normalY = hitNormalY * hitWeight; 72 | float normalZ = hitNormalZ * hitWeight; 73 | 74 | // Use crawl data to calculate normal 75 | /* 76 | Equivalent CPU code: 77 | ==================== 78 | for (unsigned int j = 0; j < hitIndex; j++) { 79 | normal += MVector(normals[sampleIds[j]]) * weights[j]; 80 | } 81 | */ 82 | for (int j = offset; j < hitIndex; j++) { 83 | float sw = sampleWeights[j]; 84 | int sampleId = sampleIds[j] * 3; 85 | normalX += driverNormals[sampleId] * sw; 86 | normalY += driverNormals[sampleId+1] * sw; 87 | normalZ += driverNormals[sampleId+2] * sw; 88 | } 89 | 90 | // Calculate the up vector 91 | /* 92 | Equivalent CPU code: 93 | ==================== 94 | up = ((points[triangleVertices[0]] + points[triangleVertices[1]]) * 0.5) - origin; 95 | */ 96 | float upX = ((driverPoints[triVertA] + driverPoints[triVertB]) * 0.5f) - originX; 97 | float upY = ((driverPoints[triVertA+1] + driverPoints[triVertB+1]) * 0.5f) - originY; 98 | float upZ = ((driverPoints[triVertA+2] + driverPoints[triVertB+2]) * 0.5f) - originZ; 99 | 100 | // Use float3 so we can use the built-in functions. We are mostly using single floats 101 | // because the preferred vector width of most gpu's these days is 1. 102 | /* 103 | Equivalent CPU code: 104 | ==================== 105 | MVector unitUp = up.normal(); 106 | // Adjust up if it's parallel to normal or if it's zero length 107 | if (abs((unitUp * normal) - 1.0) < 0.001 || up.length() < 0.0001) { 108 | for (unsigned int j = 0; j < weights.length()-1; ++j) { 109 | up -= (points[sampleIds[j]] - origin) * weights[j]; 110 | unitUp = up.normal(); 111 | if (abs((unitUp * normal) - 1.0) > 0.001 && up.length() > 0.0001) { 112 | // If the up and normal vectors are no longer parallel and the up vector has a length, 113 | // then we are good to go. 114 | break; 115 | } 116 | } 117 | up.normalize(); 118 | } else { 119 | up = unitUp; 120 | } 121 | */ 122 | float3 up = (float3)(upX, upY, upZ); 123 | float3 normal = (float3)(normalX, normalY, normalZ); 124 | normal = normalize(normal); 125 | float3 unitUp = normalize(up); 126 | float upLength = length(up); 127 | if (fabs(dot(unitUp, normal) - 1.0f) < 0.001f || upLength < 0.0001f) { 128 | for (int j = offset; j < hitIndex; j++) { 129 | float sw = sampleWeights[j]; 130 | int sampleId = sampleIds[j] * 3; 131 | up.x -= (driverPoints[sampleId] - originX) * sw; 132 | up.y -= (driverPoints[sampleId+1] - originY) * sw; 133 | up.z -= (driverPoints[sampleId+2] - originZ) * sw; 134 | unitUp = normalize(up); 135 | upLength = length(up); 136 | if (fabs(dot(unitUp, normal) - 1.0f) > 0.001f && upLength > 0.0001f) { 137 | // If the up and normal vectors are no longer parallel and the up vector has a length, 138 | // then we are good to go. 139 | break; 140 | } 141 | } 142 | up = normalize(up); 143 | } else { 144 | up = unitUp; 145 | } 146 | 147 | // Create the transform matrix 148 | // Store by columns so we can use dot to multiply with the scale matrix 149 | float3 x = cross(normal, up); 150 | float3 z = cross(x, normal); 151 | x = normalize(x); 152 | z = normalize(z); 153 | 154 | float4 matrix0 = (float4)(x.x, normal.x, z.x, originX); 155 | float4 matrix1 = (float4)(x.y, normal.y, z.y, originY); 156 | float4 matrix2 = (float4)(x.z, normal.z, z.z, originZ); 157 | float4 matrix3 = (float4)(0.0f, 0.0f, 0.0f, 1.0f); 158 | 159 | // Scale matrix mult 160 | /* 161 | Equivalent CPU code: 162 | ==================== 163 | matrix = scaleMatrix * matrix; 164 | */ 165 | __global const float4* scaleMatrix = &(drivenMatrices[8]); 166 | float4 scaleMatrix0 = (float4)(dot(scaleMatrix[0], matrix0), 167 | dot(scaleMatrix[0], matrix1), 168 | dot(scaleMatrix[0], matrix2), 169 | dot(scaleMatrix[0], matrix3)); 170 | float4 scaleMatrix1 = (float4)(dot(scaleMatrix[1], matrix0), 171 | dot(scaleMatrix[1], matrix1), 172 | dot(scaleMatrix[1], matrix2), 173 | dot(scaleMatrix[1], matrix3)); 174 | float4 scaleMatrix2 = (float4)(dot(scaleMatrix[2], matrix0), 175 | dot(scaleMatrix[2], matrix1), 176 | dot(scaleMatrix[2], matrix2), 177 | dot(scaleMatrix[2], matrix3)); 178 | float4 scaleMatrix3 = (float4)(dot(scaleMatrix[3], matrix0), 179 | dot(scaleMatrix[3], matrix1), 180 | dot(scaleMatrix[3], matrix2), 181 | dot(scaleMatrix[3], matrix3)); 182 | // Transpose so we can dot with bindMatrices 183 | float4 smX = (float4)(scaleMatrix0.x, scaleMatrix1.x, scaleMatrix2.x, scaleMatrix3.x); 184 | float4 smY = (float4)(scaleMatrix0.y, scaleMatrix1.y, scaleMatrix2.y, scaleMatrix3.y); 185 | float4 smZ = (float4)(scaleMatrix0.z, scaleMatrix1.z, scaleMatrix2.z, scaleMatrix3.z); 186 | float4 smW = (float4)(scaleMatrix0.w, scaleMatrix1.w, scaleMatrix2.w, scaleMatrix3.w); 187 | 188 | // Multiply bindMatrix with matrix 189 | /* 190 | Equivalent CPU code: 191 | ==================== 192 | MPoint newPt = ((points[i] * drivenMatrix) * (bindMatrices[index] * matrix)) * drivenInverseMatrix; 193 | */ 194 | float4 bm0 = bindMatrices[positionId*4]; 195 | float4 bm1 = bindMatrices[positionId*4+1]; 196 | float4 bm2 = bindMatrices[positionId*4+2]; 197 | float4 bm3 = bindMatrices[positionId*4+3]; 198 | float4 m0 = (float4)(dot(bm0, smX), dot(bm0, smY), dot(bm0, smZ), dot(bm0, smW)); 199 | float4 m1 = (float4)(dot(bm1, smX), dot(bm1, smY), dot(bm1, smZ), dot(bm1, smW)); 200 | float4 m2 = (float4)(dot(bm2, smX), dot(bm2, smY), dot(bm2, smZ), dot(bm2, smW)); 201 | float4 m3 = (float4)(dot(bm3, smX), dot(bm3, smY), dot(bm3, smZ), dot(bm3, smW)); 202 | 203 | float4 initialPosition = (float4)(initialPos[positionOffset], 204 | initialPos[positionOffset+1], 205 | initialPos[positionOffset+2], 206 | 1.0f); 207 | __global const float4* drivenInverseMatrix = &(drivenMatrices[4]); 208 | __global const float4* drivenMatrix = drivenMatrices; 209 | float4 worldPt = (float4)(dot(initialPosition, drivenMatrix[0]), 210 | dot(initialPosition, drivenMatrix[1]), 211 | dot(initialPosition, drivenMatrix[2]), 212 | dot(initialPosition, drivenMatrix[3])); 213 | worldPt = (float4)(dot(worldPt, (float4)(m0.x, m1.x, m2.x, m3.x)), 214 | dot(worldPt, (float4)(m0.y, m1.y, m2.y, m3.y)), 215 | dot(worldPt, (float4)(m0.z, m1.z, m2.z, m3.z)), 216 | dot(worldPt, (float4)(m0.w, m1.w, m2.w, m3.w))); 217 | float3 newPt = (float3)(dot(worldPt, drivenInverseMatrix[0]), 218 | dot(worldPt, drivenInverseMatrix[1]), 219 | dot(worldPt, drivenInverseMatrix[2])); 220 | /* 221 | Equivalent CPU code: 222 | ==================== 223 | points[i] = points[i] + ((newPt - points[i]) * paintWeights[i] * env); 224 | */ 225 | float weight = paintWeights[positionId] * envelope; 226 | finalPos[positionOffset] = initialPosition.x + ((newPt.x - initialPosition.x) * weight); 227 | finalPos[positionOffset+1] = initialPosition.y + ((newPt.y - initialPosition.y) * weight); 228 | finalPos[positionOffset+2] = initialPosition.z + ((newPt.z - initialPosition.z) * weight); 229 | } -------------------------------------------------------------------------------- /src/cvwrap.cl: -------------------------------------------------------------------------------- 1 | /* 2 | cvwrap kernel 3 | */ 4 | 5 | __kernel void cvwrap(__global float* finalPos, 6 | __global const float* initialPos, 7 | __global const float* driverPoints, 8 | __global const float* driverNormals, 9 | __global const float* paintWeights, 10 | __global const int* sampleCounts, 11 | __global const int* sampleOffsets, 12 | __global const int* sampleIds, 13 | __global const float* sampleWeights, 14 | __global const int* triangleVerts, 15 | __global const float* baryCoords, 16 | __global const float4* bindMatrices, 17 | __global const float4* scaleMatrix, 18 | __global const float* drivenWorldMatrix, 19 | __global const float* drivenInvMatrix, 20 | const float envelope, 21 | const uint positionCount) { 22 | unsigned int positionId = get_global_id(0); 23 | if (positionId >= positionCount) { 24 | return; 25 | } 26 | unsigned int positionOffset = positionId * 3; 27 | 28 | // Start with the recreated point and normal using the barycentric coordinates of the hit point. 29 | /* 30 | Equivalent CPU code: 31 | ==================== 32 | MVector hitNormal; 33 | for (int i = 0; i < 3; ++i) { 34 | origin += points[triangleVertices[i]] * coords[i]; 35 | hitNormal += MVector(normals[triangleVertices[i]]) * coords[i]; 36 | } 37 | */ 38 | float baryA = baryCoords[positionOffset]; 39 | float baryB = baryCoords[positionOffset+1]; 40 | float baryC = baryCoords[positionOffset+2]; 41 | int triVertA = triangleVerts[positionOffset] * 3; 42 | int triVertB = triangleVerts[positionOffset+1] * 3; 43 | int triVertC = triangleVerts[positionOffset+2] * 3; 44 | float originX = driverPoints[triVertA] * baryA + 45 | driverPoints[triVertB] * baryB + 46 | driverPoints[triVertC] * baryC; 47 | float originY = driverPoints[triVertA+1] * baryA + 48 | driverPoints[triVertB+1] * baryB + 49 | driverPoints[triVertC+1] * baryC; 50 | float originZ = driverPoints[triVertA+2] * baryA + 51 | driverPoints[triVertB+2] * baryB + 52 | driverPoints[triVertC+2] * baryC; 53 | float hitNormalX = driverNormals[triVertA] * baryA + 54 | driverNormals[triVertB] * baryB + 55 | driverNormals[triVertC] * baryC; 56 | float hitNormalY = driverNormals[triVertA+1] * baryA + 57 | driverNormals[triVertB+1] * baryB + 58 | driverNormals[triVertC+1] * baryC; 59 | float hitNormalZ = driverNormals[triVertA+2] * baryA + 60 | driverNormals[triVertB+2] * baryB + 61 | driverNormals[triVertC+2] * baryC; 62 | 63 | /* 64 | Equivalent CPU code: 65 | ==================== 66 | unsigned int hitIndex = weights.length()-1; 67 | normal = hitNormal * weights[hitIndex]; 68 | */ 69 | int offset = sampleOffsets[positionId]; 70 | int hitIndex = offset + sampleCounts[positionId] - 1; 71 | float hitWeight = sampleWeights[hitIndex]; 72 | float normalX = hitNormalX * hitWeight; 73 | float normalY = hitNormalY * hitWeight; 74 | float normalZ = hitNormalZ * hitWeight; 75 | 76 | // Use crawl data to calculate normal 77 | /* 78 | Equivalent CPU code: 79 | ==================== 80 | for (unsigned int j = 0; j < hitIndex; j++) { 81 | normal += MVector(normals[sampleIds[j]]) * weights[j]; 82 | } 83 | */ 84 | for (int j = offset; j < hitIndex; j++) { 85 | float sw = sampleWeights[j]; 86 | int sampleId = sampleIds[j] * 3; 87 | normalX += driverNormals[sampleId] * sw; 88 | normalY += driverNormals[sampleId+1] * sw; 89 | normalZ += driverNormals[sampleId+2] * sw; 90 | } 91 | 92 | // Calculate the up vector 93 | /* 94 | Equivalent CPU code: 95 | ==================== 96 | up = ((points[triangleVertices[0]] + points[triangleVertices[1]]) * 0.5) - origin; 97 | */ 98 | float upX = ((driverPoints[triVertA] + driverPoints[triVertB]) * 0.5f) - originX; 99 | float upY = ((driverPoints[triVertA+1] + driverPoints[triVertB+1]) * 0.5f) - originY; 100 | float upZ = ((driverPoints[triVertA+2] + driverPoints[triVertB+2]) * 0.5f) - originZ; 101 | 102 | // Use float3 so we can use the built-in functions. We are mostly using single floats 103 | // because the preferred vector width of most gpu's these days is 1. 104 | /* 105 | Equivalent CPU code: 106 | ==================== 107 | MVector unitUp = up.normal(); 108 | // Adjust up if it's parallel to normal or if it's zero length 109 | if (abs((unitUp * normal) - 1.0) < 0.001 || up.length() < 0.0001) { 110 | for (unsigned int j = 0; j < weights.length()-1; ++j) { 111 | up -= (points[sampleIds[j]] - origin) * weights[j]; 112 | unitUp = up.normal(); 113 | if (abs((unitUp * normal) - 1.0) > 0.001 && up.length() > 0.0001) { 114 | // If the up and normal vectors are no longer parallel and the up vector has a length, 115 | // then we are good to go. 116 | break; 117 | } 118 | } 119 | up.normalize(); 120 | } else { 121 | up = unitUp; 122 | } 123 | */ 124 | float3 up = (float3)(upX, upY, upZ); 125 | float3 normal = (float3)(normalX, normalY, normalZ); 126 | normal = normalize(normal); 127 | float3 unitUp = normalize(up); 128 | float upLength = length(up); 129 | if (fabs(dot(unitUp, normal) - 1.0f) < 0.001f || upLength < 0.0001f) { 130 | for (int j = offset; j < hitIndex; j++) { 131 | float sw = sampleWeights[j]; 132 | int sampleId = sampleIds[j] * 3; 133 | up.x -= (driverPoints[sampleId] - originX) * sw; 134 | up.y -= (driverPoints[sampleId+1] - originY) * sw; 135 | up.z -= (driverPoints[sampleId+2] - originZ) * sw; 136 | unitUp = normalize(up); 137 | upLength = length(up); 138 | if (fabs(dot(unitUp, normal) - 1.0f) > 0.001f && upLength > 0.0001f) { 139 | // If the up and normal vectors are no longer parallel and the up vector has a length, 140 | // then we are good to go. 141 | break; 142 | } 143 | } 144 | up = normalize(up); 145 | } else { 146 | up = unitUp; 147 | } 148 | 149 | // Create the transform matrix 150 | // Store by columns so we can use dot to multiply with the scale matrix 151 | float3 x = cross(normal, up); 152 | float3 z = cross(x, normal); 153 | x = normalize(x); 154 | z = normalize(z); 155 | 156 | float4 matrix0 = (float4)(x.x, normal.x, z.x, originX); 157 | float4 matrix1 = (float4)(x.y, normal.y, z.y, originY); 158 | float4 matrix2 = (float4)(x.z, normal.z, z.z, originZ); 159 | float4 matrix3 = (float4)(0.0f, 0.0f, 0.0f, 1.0f); 160 | 161 | // Scale matrix mult 162 | /* 163 | Equivalent CPU code: 164 | ==================== 165 | matrix = scaleMatrix * matrix; 166 | */ 167 | float4 scaleMatrix0 = (float4)(dot(scaleMatrix[0], matrix0), 168 | dot(scaleMatrix[0], matrix1), 169 | dot(scaleMatrix[0], matrix2), 170 | dot(scaleMatrix[0], matrix3)); 171 | float4 scaleMatrix1 = (float4)(dot(scaleMatrix[1], matrix0), 172 | dot(scaleMatrix[1], matrix1), 173 | dot(scaleMatrix[1], matrix2), 174 | dot(scaleMatrix[1], matrix3)); 175 | float4 scaleMatrix2 = (float4)(dot(scaleMatrix[2], matrix0), 176 | dot(scaleMatrix[2], matrix1), 177 | dot(scaleMatrix[2], matrix2), 178 | dot(scaleMatrix[2], matrix3)); 179 | float4 scaleMatrix3 = (float4)(dot(scaleMatrix[3], matrix0), 180 | dot(scaleMatrix[3], matrix1), 181 | dot(scaleMatrix[3], matrix2), 182 | dot(scaleMatrix[3], matrix3)); 183 | // Transpose so we can dot with bindMatrices 184 | float4 smX = (float4)(scaleMatrix0.x, scaleMatrix1.x, scaleMatrix2.x, scaleMatrix3.x); 185 | float4 smY = (float4)(scaleMatrix0.y, scaleMatrix1.y, scaleMatrix2.y, scaleMatrix3.y); 186 | float4 smZ = (float4)(scaleMatrix0.z, scaleMatrix1.z, scaleMatrix2.z, scaleMatrix3.z); 187 | float4 smW = (float4)(scaleMatrix0.w, scaleMatrix1.w, scaleMatrix2.w, scaleMatrix3.w); 188 | 189 | // Multiply bindMatrix with matrix 190 | /* 191 | Equivalent CPU code: 192 | ==================== 193 | MPoint newPt = ((points[i] * drivenMatrix) * (bindMatrices[index] * matrix)) * drivenInverseMatrix; 194 | */ 195 | float4 bm0 = bindMatrices[positionId*4]; 196 | float4 bm1 = bindMatrices[positionId*4+1]; 197 | float4 bm2 = bindMatrices[positionId*4+2]; 198 | float4 bm3 = bindMatrices[positionId*4+3]; 199 | float4 m0 = (float4)(dot(bm0, smX), dot(bm0, smY), dot(bm0, smZ), dot(bm0, smW)); 200 | float4 m1 = (float4)(dot(bm1, smX), dot(bm1, smY), dot(bm1, smZ), dot(bm1, smW)); 201 | float4 m2 = (float4)(dot(bm2, smX), dot(bm2, smY), dot(bm2, smZ), dot(bm2, smW)); 202 | float4 m3 = (float4)(dot(bm3, smX), dot(bm3, smY), dot(bm3, smZ), dot(bm3, smW)); 203 | 204 | float4 initialPosition = (float4)(initialPos[positionOffset], 205 | initialPos[positionOffset+1], 206 | initialPos[positionOffset+2], 207 | 1.0f); 208 | 209 | float4 drivenMatrixTransposed[4]; 210 | float4 drivenInvMatrixTransposed[4]; 211 | for (uint i=0; i < 4; i++) 212 | { 213 | drivenMatrixTransposed[i] = (float4)(drivenWorldMatrix[i], drivenWorldMatrix[i+4], drivenWorldMatrix[i+8], drivenWorldMatrix[i+12]); 214 | drivenInvMatrixTransposed[i] = (float4)(drivenInvMatrix[i], drivenInvMatrix[i+4], drivenInvMatrix[i+8], drivenInvMatrix[i+12]); 215 | } 216 | 217 | float4 worldPt = (float4)(dot(initialPosition, drivenMatrixTransposed[0]), 218 | dot(initialPosition, drivenMatrixTransposed[1]), 219 | dot(initialPosition, drivenMatrixTransposed[2]), 220 | dot(initialPosition, drivenMatrixTransposed[3])); 221 | worldPt = (float4)(dot(worldPt, (float4)(m0.x, m1.x, m2.x, m3.x)), 222 | dot(worldPt, (float4)(m0.y, m1.y, m2.y, m3.y)), 223 | dot(worldPt, (float4)(m0.z, m1.z, m2.z, m3.z)), 224 | dot(worldPt, (float4)(m0.w, m1.w, m2.w, m3.w))); 225 | float3 newPt = (float3)(dot(worldPt, drivenInvMatrixTransposed[0]), 226 | dot(worldPt, drivenInvMatrixTransposed[1]), 227 | dot(worldPt, drivenInvMatrixTransposed[2])); 228 | /* 229 | Equivalent CPU code: 230 | ==================== 231 | points[i] = points[i] + ((newPt - points[i]) * paintWeights[i] * env); 232 | */ 233 | float weight = paintWeights[positionId] * envelope; 234 | finalPos[positionOffset] = initialPosition.x + ((newPt.x - initialPosition.x) * weight); 235 | finalPos[positionOffset+1] = initialPosition.y + ((newPt.y - initialPosition.y) * weight); 236 | finalPos[positionOffset+2] = initialPosition.z + ((newPt.z - initialPosition.z) * weight); 237 | } -------------------------------------------------------------------------------- /scripts/cvwrap/menu.py: -------------------------------------------------------------------------------- 1 | import maya.cmds as cmds 2 | import maya.mel as mel 3 | import maya.OpenMayaUI as OpenMayaUI 4 | import os 5 | if cmds.about(api=True) >= 201700: 6 | from PySide2 import QtGui 7 | else: 8 | from PySide import QtGui 9 | import cvwrap.bindui 10 | 11 | NAME_WIDGET = 'cvwrap_name' 12 | RADIUS_WIDGET = 'cvwrap_radius' 13 | NEW_BIND_MESH_WIDGET = 'cvwrap_newbindmesh' 14 | BIND_FILE_WIDGET = 'cvwrap_bindfile' 15 | MENU_ITEMS = [] 16 | 17 | 18 | def create_menuitems(): 19 | global MENU_ITEMS 20 | if MENU_ITEMS: 21 | # Already created 22 | return 23 | if cmds.about(api=True) < 201600: 24 | cmds.warning('cvWrap menus only available in Maya 2016 and higher.') 25 | return 26 | for menu in ['mainDeformMenu', 'mainRigDeformationsMenu']: 27 | # Make sure the menu widgets exist first. 28 | mel.eval('ChaDeformationsMenu MayaWindow|{0};'.format(menu)) 29 | items = cmds.menu(menu, q=True, ia=True) 30 | for item in items: 31 | if cmds.menuItem(item, q=True, divider=True): 32 | section = cmds.menuItem(item, q=True, label=True) 33 | menu_label = cmds.menuItem(item, q=True, label=True) 34 | if menu_label == 'Wrap': 35 | if section == 'Create': 36 | cvwrap_item = cmds.menuItem(label="cvWrap", command=create_cvwrap, 37 | sourceType='python', insertAfter=item, parent=menu) 38 | cvwrap_options = cmds.menuItem(command=display_cvwrap_options, 39 | insertAfter=cvwrap_item, parent=menu, 40 | optionBox=True) 41 | MENU_ITEMS.append(cvwrap_item) 42 | MENU_ITEMS.append(cvwrap_options) 43 | elif section == 'Edit': 44 | submenu = cmds.menuItem(label="cvWrap", subMenu=True, insertAfter=item, 45 | parent=menu) 46 | MENU_ITEMS.append(submenu) 47 | item = cmds.menuItem(label="Edit Binding", command=edit_binding, 48 | sourceType='python', parent=submenu) 49 | MENU_ITEMS.append(item) 50 | item = cmds.menuItem(label="Import Binding", command=import_binding, 51 | sourceType='python', parent=submenu) 52 | MENU_ITEMS.append(item) 53 | item = cmds.menuItem(label="Export Binding", command=export_binding, 54 | sourceType='python', parent=submenu) 55 | MENU_ITEMS.append(item) 56 | elif menu_label == 'Cluster' and section == 'Paint Weights': 57 | item = cmds.menuItem(label="cvWrap", command=paint_cvwrap_weights, 58 | sourceType='python', insertAfter=item, parent=menu) 59 | MENU_ITEMS.append(item) 60 | 61 | 62 | def create_cvwrap(*args, **kwargs): 63 | cmds.loadPlugin('cvwrap', qt=True) 64 | sel = cmds.ls(sl=True) 65 | if len(sel) >= 2: 66 | kwargs = get_create_command_kwargs() 67 | result = cmds.cvWrap(**kwargs) 68 | print(result) 69 | else: 70 | raise RuntimeError("Select at least one surface and one influence object.") 71 | 72 | 73 | def get_create_command_kwargs(): 74 | """Gets the cvWrap command arguments either from the option box widgets or the saved 75 | option vars. If the widgets exist, their values will be saved to the option vars. 76 | @return A dictionary of the kwargs to the cvWrap command.""" 77 | args = {} 78 | if cmds.textFieldGrp(NAME_WIDGET, exists=True): 79 | args['name'] = cmds.textFieldGrp(NAME_WIDGET, q=True, text=True) 80 | cmds.optionVar(sv=(NAME_WIDGET, args['name'])) 81 | else: 82 | args['name'] = cmds.optionVar(q=NAME_WIDGET) or 'cvWrap#' 83 | if cmds.floatSliderGrp(RADIUS_WIDGET, exists=True): 84 | args['radius'] = cmds.floatSliderGrp(RADIUS_WIDGET, q=True, value=True) 85 | cmds.optionVar(fv=(RADIUS_WIDGET, args['radius'])) 86 | else: 87 | args['radius'] = cmds.optionVar(q=RADIUS_WIDGET) 88 | 89 | if cmds.checkBoxGrp(NEW_BIND_MESH_WIDGET, exists=True): 90 | if cmds.checkBoxGrp(NEW_BIND_MESH_WIDGET, q=True, v1=True): 91 | args['newBindMesh'] = True 92 | cmds.optionVar(iv=(NEW_BIND_MESH_WIDGET, 1)) 93 | else: 94 | cmds.optionVar(iv=(NEW_BIND_MESH_WIDGET, 0)) 95 | else: 96 | value = cmds.optionVar(q=NEW_BIND_MESH_WIDGET) 97 | if value: 98 | args['newBindMesh'] = True 99 | 100 | if cmds.textFieldButtonGrp(BIND_FILE_WIDGET, exists=True): 101 | bind_file = cmds.textFieldButtonGrp(BIND_FILE_WIDGET, q=True, text=True) 102 | bind_file = os.path.expandvars(bind_file.strip()) 103 | if bind_file: 104 | if os.path.exists(bind_file): 105 | args['binding'] = bind_file 106 | else: 107 | cmds.warning('{0} does not exist.'.format(bind_file)) 108 | 109 | return args 110 | 111 | 112 | def display_cvwrap_options(*args, **kwargs): 113 | cmds.loadPlugin('cvwrap', qt=True) 114 | layout = mel.eval('getOptionBox') 115 | cmds.setParent(layout) 116 | cmds.columnLayout(adj=True) 117 | 118 | for widget in [NAME_WIDGET, RADIUS_WIDGET, BIND_FILE_WIDGET, NEW_BIND_MESH_WIDGET]: 119 | # Delete the widgets so we don't create multiple controls with the same name 120 | try: 121 | cmds.deleteUI(widget, control=True) 122 | except: 123 | pass 124 | 125 | cmds.textFieldGrp(NAME_WIDGET, label='Node name', text='cvWrap#') 126 | radius = cmds.optionVar(q=RADIUS_WIDGET) 127 | cmds.floatSliderGrp(RADIUS_WIDGET, label='Sample radius', field=True, minValue=0.0, 128 | maxValue=100.0, fieldMinValue=0.0, fieldMaxValue=100.0, value=radius, 129 | step=0.01, precision=2) 130 | cmds.textFieldButtonGrp(BIND_FILE_WIDGET, label='Binding file ', text='', buttonLabel='Browse', 131 | bc=display_bind_file_dialog) 132 | use_new_bind_mesh = cmds.optionVar(q=NEW_BIND_MESH_WIDGET) 133 | cmds.checkBoxGrp(NEW_BIND_MESH_WIDGET, numberOfCheckBoxes=1, label='Create new bind mesh', 134 | v1=use_new_bind_mesh) 135 | mel.eval('setOptionBoxTitle("cvWrap Options");') 136 | mel.eval('setOptionBoxCommandName("cvWrap");') 137 | apply_close_button = mel.eval('getOptionBoxApplyAndCloseBtn;') 138 | cmds.button(apply_close_button, e=True, command=apply_and_close) 139 | apply_button = mel.eval('getOptionBoxApplyBtn;') 140 | cmds.button(apply_button, e=True, command=create_cvwrap) 141 | reset_button = mel.eval('getOptionBoxResetBtn;') 142 | # For some reason, the buttons in the menu only accept MEL. 143 | cmds.button(reset_button, e=True, 144 | command='python("import cvwrap.menu; cvwrap.menu.reset_to_defaults()");') 145 | close_button = mel.eval('getOptionBoxCloseBtn;') 146 | cmds.button(close_button, e=True, command=close_option_box) 147 | save_button = mel.eval('getOptionBoxSaveBtn;') 148 | cmds.button(save_button, e=True, 149 | command='python("import cvwrap.menu; cvwrap.menu.get_create_command_kwargs()");') 150 | mel.eval('showOptionBox') 151 | 152 | 153 | def apply_and_close(*args, **kwargs): 154 | """Create the cvWrap deformer and close the option box.""" 155 | create_cvwrap() 156 | mel.eval('saveOptionBoxSize') 157 | close_option_box() 158 | 159 | 160 | def close_option_box(*args, **kwargs): 161 | mel.eval('hideOptionBox') 162 | 163 | 164 | def display_bind_file_dialog(*args, **kwargs): 165 | """Displays the dialog to choose the binding file with which to create the cvWrap deformer.""" 166 | root_dir = cmds.workspace(q=True, rootDirectory=True) 167 | start_directory = os.path.join(root_dir, 'data') 168 | file_path = cmds.fileDialog2(fileFilter='*.wrap', dialogStyle=2, fileMode=1, 169 | startingDirectory=start_directory) 170 | if file_path: 171 | cmds.textFieldButtonGrp(BIND_FILE_WIDGET, e=True, text=file_path[0]) 172 | 173 | 174 | def reset_to_defaults(*args, **kwargs): 175 | """Reset the cvWrap option box widgets to their defaults.""" 176 | cmds.textFieldGrp(NAME_WIDGET, e=True, text='cvWrap#') 177 | cmds.floatSliderGrp(RADIUS_WIDGET, e=True, value=0) 178 | cmds.textFieldButtonGrp(BIND_FILE_WIDGET, e=True, text='') 179 | cmds.checkBoxGrp(NEW_BIND_MESH_WIDGET, e=True, v1=False) 180 | 181 | 182 | def edit_binding(*args, **kwargs): 183 | cvwrap.bindui.show() 184 | 185 | 186 | def export_binding(*args, **kwargs): 187 | """Export a wrap binding from the selected wrap node or mesh.""" 188 | cmds.loadPlugin('cvwrap', qt=True) 189 | wrap_node = get_wrap_node_from_selected() 190 | if wrap_node: 191 | data_dir = os.path.join(cmds.workspace(q=True, rd=True), 'data') 192 | file_path = cmds.fileDialog2(fileFilter='*.wrap', dialogStyle=2, cap='Export Binding', 193 | startingDirectory=data_dir, fm=0) 194 | if file_path: 195 | cmds.cvWrap(wrap_node, ex=file_path[0]) 196 | 197 | 198 | def import_binding(*args, **kwargs): 199 | """Import a wrap binding onto the selected wrap node or mesh.""" 200 | cmds.loadPlugin('cvwrap', qt=True) 201 | wrap_node = get_wrap_node_from_selected() 202 | if wrap_node: 203 | data_dir = os.path.join(cmds.workspace(q=True, rd=True), 'data') 204 | file_path = cmds.fileDialog2(fileFilter='*.wrap', dialogStyle=2, cap='Import Binding', 205 | startingDirectory=data_dir, fm=1) 206 | if file_path: 207 | cmds.cvWrap(wrap_node, im=file_path[0]) 208 | 209 | 210 | def get_wrap_node_from_selected(): 211 | """Get a wrap node from the selected geometry.""" 212 | sel = cmds.ls(sl=True) or [] 213 | if not sel: 214 | raise RuntimeError('No cvWrap found on selected.') 215 | if cmds.nodeType(sel[0]) == 'cvWrap': 216 | return sel[0] 217 | history = cmds.listHistory(sel[0], pdo=0) or [] 218 | wrap_nodes = [node for node in history if cmds.nodeType(node) == 'cvWrap'] 219 | if not wrap_nodes: 220 | raise RuntimeError('No cvWrap node found on {0}.'.format(sel[0])) 221 | if len(wrap_nodes) == 1: 222 | return wrap_nodes[0] 223 | else: 224 | # Multiple wrap nodes are deforming the mesh. Let the user choose which one 225 | # to use. 226 | return QtGui.QInputDialog.getItem(None, 'Select cvWrap node', 'cvWrap node:', wrap_nodes) 227 | 228 | 229 | def destroy_menuitems(): 230 | """Remove the cvWrap items from the menus.""" 231 | global MENU_ITEMS 232 | for item in MENU_ITEMS: 233 | cmds.deleteUI(item, menuItem=True) 234 | MENU_ITEMS = [] 235 | 236 | 237 | def paint_cvwrap_weights(*args, **kwargs): 238 | """Activates the paint cvWrap weights context.""" 239 | sel = cmds.ls(sl=True) 240 | if sel: 241 | wrap_node = get_wrap_node_from_selected() 242 | if wrap_node: 243 | mel.eval('artSetToolAndSelectAttr("artAttrCtx", "cvWrap.{0}.weights");'.format( 244 | wrap_node)) 245 | -------------------------------------------------------------------------------- /src/common.cpp: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define NORMALIZATION_INDEX -1 16 | 17 | void StartProgress(const MString& title, unsigned int count) { 18 | if (MGlobal::mayaState() == MGlobal::kInteractive) { 19 | MString message = "progressBar -e -bp -ii true -st \""; 20 | message += title; 21 | message += "\" -max "; 22 | message += count; 23 | message += " $gMainProgressBar;"; 24 | MGlobal::executeCommand(message); 25 | } 26 | } 27 | 28 | 29 | void StepProgress(int step) { 30 | if (MGlobal::mayaState() == MGlobal::kInteractive) { 31 | MString message = "progressBar -e -s "; 32 | message += step; 33 | message += " $gMainProgressBar;"; 34 | MGlobal::executeCommand(message); 35 | } 36 | } 37 | 38 | 39 | bool ProgressCancelled() { 40 | if (MGlobal::mayaState() == MGlobal::kInteractive) { 41 | int cmdResult = 0; 42 | MGlobal::executeCommand("progressBar -query -isCancelled $gMainProgressBar", cmdResult); 43 | return cmdResult != 0; 44 | } 45 | return false; 46 | } 47 | 48 | 49 | void EndProgress() { 50 | if (MGlobal::mayaState() == MGlobal::kInteractive) { 51 | MGlobal::executeCommand("progressBar -e -ep $gMainProgressBar;"); 52 | } 53 | } 54 | 55 | 56 | bool IsShapeNode(MDagPath& path) { 57 | return path.node().hasFn(MFn::kMesh) || 58 | path.node().hasFn(MFn::kNurbsCurve) || 59 | path.node().hasFn(MFn::kNurbsSurface); 60 | } 61 | 62 | 63 | MStatus GetShapeNode(MDagPath& path, bool intermediate) { 64 | MStatus status; 65 | 66 | if (IsShapeNode(path)) { 67 | // Start at the transform so we can honor the intermediate flag. 68 | path.pop(); 69 | } 70 | 71 | if (path.hasFn(MFn::kTransform)) { 72 | unsigned int shapeCount = path.childCount(); 73 | 74 | for (unsigned int i = 0; i < shapeCount; ++i) { 75 | status = path.push(path.child(i)); 76 | CHECK_MSTATUS_AND_RETURN_IT(status); 77 | if (!IsShapeNode(path)) { 78 | path.pop(); 79 | continue; 80 | } 81 | 82 | MFnDagNode fnNode(path, &status); 83 | CHECK_MSTATUS_AND_RETURN_IT(status); 84 | if ((!fnNode.isIntermediateObject() && !intermediate) || 85 | (fnNode.isIntermediateObject() && intermediate)) { 86 | return MS::kSuccess; 87 | } 88 | // Go to the next shape 89 | path.pop(); 90 | } 91 | } 92 | 93 | // No valid shape node found. 94 | return MS::kFailure; 95 | } 96 | 97 | 98 | MStatus GetDagPath(MString& name, MDagPath& path) { 99 | MStatus status; 100 | MSelectionList list; 101 | status = MGlobal::getSelectionListByName(name, list); 102 | CHECK_MSTATUS_AND_RETURN_IT(status); 103 | status = list.getDagPath(0, path); 104 | CHECK_MSTATUS_AND_RETURN_IT(status); 105 | return MS::kSuccess; 106 | } 107 | 108 | MStatus DeleteIntermediateObjects(MDagPath& path) { 109 | MStatus status; 110 | MDagPath pathMesh(path); 111 | while (GetShapeNode(pathMesh, true) == MS::kSuccess) { 112 | status = MGlobal::executeCommand("delete " + pathMesh.partialPathName()); 113 | CHECK_MSTATUS_AND_RETURN_IT(status); 114 | pathMesh = MDagPath(path); 115 | } 116 | return MS::kSuccess; 117 | } 118 | 119 | void GetBarycentricCoordinates(const MPoint& P, const MPoint& A, const MPoint& B, const MPoint& C, 120 | BaryCoords& coords) { 121 | // Compute the normal of the triangle 122 | MVector N = (B - A) ^ (C - A); 123 | MVector unitN = N.normal(); 124 | 125 | // Compute twice area of triangle ABC 126 | double areaABC = unitN * N; 127 | 128 | if (areaABC == 0.0) { 129 | // If the triangle is degenerate, just use one of the points. 130 | coords[0] = 1.0f; 131 | coords[1] = 0.0f; 132 | coords[2] = 0.0f; 133 | return; 134 | } 135 | 136 | // Compute a 137 | double areaPBC = unitN * ((B - P) ^ (C - P)); 138 | coords[0] = (float)(areaPBC / areaABC); 139 | 140 | // Compute b 141 | double areaPCA = unitN * ((C - P) ^ (A - P)); 142 | coords[1] = (float)(areaPCA / areaABC); 143 | 144 | // Compute c 145 | coords[2] = 1.0f - coords[0] - coords[1]; 146 | } 147 | 148 | 149 | MStatus GetAdjacency(MDagPath& pathMesh, std::vector >& adjacency) { 150 | MStatus status; 151 | // Get mesh adjacency. The adjacency will be all vertex ids on the connected faces. 152 | MItMeshVertex itVert(pathMesh, MObject::kNullObj, &status); 153 | CHECK_MSTATUS_AND_RETURN_IT(status); 154 | MFnMesh fnMesh(pathMesh, &status); 155 | CHECK_MSTATUS_AND_RETURN_IT(status); 156 | adjacency.resize(itVert.count()); 157 | for (; !itVert.isDone(); itVert.next()) { 158 | MIntArray faces; 159 | status = itVert.getConnectedFaces(faces); 160 | CHECK_MSTATUS_AND_RETURN_IT(status); 161 | adjacency[itVert.index()].clear(); 162 | // Put the vertex ids in a set to avoid duplicates 163 | for (unsigned int j = 0; j < faces.length(); ++j) { 164 | MIntArray vertices; 165 | fnMesh.getPolygonVertices(faces[j], vertices); 166 | for (unsigned int k = 0; k < vertices.length(); ++k) { 167 | if (vertices[k] != itVert.index()) { 168 | adjacency[itVert.index()].insert(vertices[k]); 169 | } 170 | } 171 | } 172 | } 173 | return MS::kSuccess; 174 | } 175 | 176 | 177 | /** 178 | Used in the CrawlSurface function to keep track of where we are crawling. 179 | */ 180 | struct CrawlData { 181 | MPoint sourcePosition; /**< Where the crawl iteration came from. */ 182 | double crawlDistance; /**< How far this crawl iteration has traveled. */ 183 | int nextVertex; /**< Where this crawl iteration should go next. */ 184 | }; 185 | 186 | 187 | MStatus CrawlSurface(const MPoint& startPoint, const MIntArray& vertexIndices, MPointArray& points, double maxDistance, 188 | std::vector >& adjacency, std::map& distances) { 189 | MStatus status; 190 | distances[NORMALIZATION_INDEX] = 0.0; // -1 will represent our hit point. 191 | double minStartDistance = 999999.0; 192 | unsigned int minStartIndex = 0; 193 | 194 | // Instead of a recursive function, which can get pretty slow, we'll use a queue to keep 195 | // track of where we are going and where we are coming from. 196 | std::queue verticesToVisit; 197 | // Add the initial crawl paths to the queue. 198 | for (unsigned int i = 0; i < vertexIndices.length(); ++i) { 199 | double distance = startPoint.distanceTo(points[vertexIndices[i]]); 200 | // Only crawl to the starting vertices if they are within the radius. 201 | if (distance <= maxDistance) { 202 | CrawlData root = {startPoint, distance, vertexIndices[i]}; 203 | verticesToVisit.push(root); 204 | } 205 | // Track the minimum start distance in case we need to add the closest vertex below. 206 | // The minimum must be greater than 0 to make sure we do not use the vertex that is the 207 | // same as the startPoint which would create an invalid up vector. 208 | if (distance < minStartDistance && distance > 0.000001) { 209 | minStartDistance = distance; 210 | minStartIndex = vertexIndices[i]; 211 | } 212 | } 213 | // If we didn't even reach a vertex in the hit face, or the startPoint is equal to a vertex 214 | // on the face, add the closest vertex so we can calculate a proper up vector 215 | if (verticesToVisit.size() <= 1) { 216 | CrawlData root = {startPoint, maxDistance - 0.001, (int)minStartIndex}; 217 | verticesToVisit.push(root); 218 | distances[minStartIndex] = maxDistance - 0.001; 219 | } 220 | while (verticesToVisit.size()) { 221 | CrawlData next = verticesToVisit.front(); 222 | verticesToVisit.pop(); 223 | 224 | // Extract the data out of the crawl struct 225 | int idx = next.nextVertex; 226 | MPoint& pt = points[idx]; 227 | MPoint sourcePoint = next.sourcePosition; 228 | double currentCrawlDistance = next.crawlDistance; 229 | 230 | currentCrawlDistance += sourcePoint.distanceTo(pt); 231 | if (currentCrawlDistance >= maxDistance) { 232 | // If this vertex is outside the radius, no need to crawl anymore from that vertex. 233 | continue; 234 | } 235 | double& savedDistance = distances[idx]; 236 | if (currentCrawlDistance <= savedDistance || savedDistance == 0.0) { 237 | // If this current crawl distance is less then the distance we have saved for this 238 | // vertex, use this new crawl distance instead. 239 | savedDistance = currentCrawlDistance; 240 | } else { 241 | // A smaller distance is already stored so we don't want to crawl 242 | // from this vertex any further. 243 | continue; 244 | } 245 | // Crawl the adjacent vertices 246 | std::set::iterator iter; 247 | for (iter = adjacency[idx].begin(); iter != adjacency[idx].end(); ++iter) { 248 | CrawlData data = {pt, currentCrawlDistance, *iter}; 249 | verticesToVisit.push(data); 250 | } 251 | } 252 | assert(distances.size() > 0); 253 | 254 | return MS::kSuccess; 255 | } 256 | 257 | bool SampleSort(std::pair lhs, std::pair rhs) { 258 | // Ensure that the normalization sample comes last. 259 | return (lhs.second < rhs.second) || rhs.first == NORMALIZATION_INDEX; 260 | } 261 | 262 | void CalculateSampleWeights(const std::map& distances, double radius, 263 | MIntArray& vertexIds, MDoubleArray& weights) { 264 | 265 | std::map::const_iterator itDistance; 266 | std::vector > samples; 267 | for (itDistance = distances.begin(); 268 | itDistance != distances.end(); 269 | itDistance++) { 270 | double x = itDistance->second; 271 | double w = 1.0 - (x/radius); 272 | samples.push_back(std::pair(itDistance->first, w)); 273 | } 274 | 275 | // Make the samples a multiple of 4 so we can use fast intrinsics! 276 | int remainder = 4 - ((samples.size()-1) % 4); 277 | if (remainder != 4) { 278 | for (int i = 0; i < remainder; ++i) { 279 | samples.push_back(std::pair(0, 0.0)); 280 | } 281 | } 282 | 283 | unsigned int length = (unsigned int)samples.size(); 284 | weights.setLength(length); 285 | vertexIds.setLength(length); 286 | std::sort(samples.begin(), samples.end(), SampleSort); 287 | std::vector >::iterator iter; 288 | int ii = 0; 289 | double sum = 0.0; 290 | for (iter = samples.begin(); iter != samples.end(); ++iter, ++ii) { 291 | vertexIds[ii] = (*iter).first; 292 | weights[ii] = (*iter).second; 293 | sum += (*iter).second; 294 | } 295 | assert(sum > 0.0); 296 | // Normalize the weights 297 | for (unsigned int i = 0; i < weights.length(); ++i) { 298 | weights[i] /= sum; 299 | } 300 | } 301 | 302 | 303 | void CreateMatrix(const MPoint& origin, const MVector& normal, const MVector& up, 304 | MMatrix& matrix) { 305 | const MPoint& t = origin; 306 | const MVector& y = normal; 307 | MVector x = y ^ up; 308 | MVector z = x ^ y; 309 | // Renormalize vectors 310 | x.normalize(); 311 | z.normalize(); 312 | matrix[0][0] = x.x; matrix[0][1] = x.y; matrix[0][2] = x.z; matrix[0][3] = 0.0; 313 | matrix[1][0] = y.x; matrix[1][1] = y.y; matrix[1][2] = y.z; matrix[1][3] = 0.0; 314 | matrix[2][0] = z.x; matrix[2][1] = z.y; matrix[2][2] = z.z; matrix[2][3] = 0.0; 315 | matrix[3][0] = t.x; matrix[3][1] = t.y; matrix[3][2] = t.z; matrix[3][3] = 1.0; 316 | } 317 | 318 | 319 | void CalculateBasisComponents(const MDoubleArray& weights, const BaryCoords& coords, 320 | const MIntArray& triangleVertices, const MPointArray& points, 321 | const MFloatVectorArray& normals, const MIntArray& sampleIds, 322 | double* alignedStorage, 323 | MPoint& origin, MVector& up, MVector& normal) { 324 | // Start with the recreated point and normal using the barycentric coordinates of the hit point. 325 | unsigned int hitIndex = weights.length()-1; 326 | #ifdef __AVX__ 327 | __m256d originV = Dot4(coords[0], coords[1], coords[2], 0.0, 328 | points[triangleVertices[0]], points[triangleVertices[1]], 329 | points[triangleVertices[2]], MPoint::origin); 330 | __m256d hitNormalV = Dot4(coords[0], coords[1], coords[2], 0.0, 331 | normals[triangleVertices[0]], normals[triangleVertices[1]], 332 | normals[triangleVertices[2]], MVector::zero); 333 | __m256d hitWeightV = _mm256_set1_pd(weights[hitIndex]); 334 | // Create the barycentric point and normal. 335 | __m256d normalV = _mm256_mul_pd(hitNormalV, hitWeightV); 336 | // Then use the weighted adjacent data. 337 | for (unsigned int j = 0; j < hitIndex; j += 4) { 338 | __m256d tempNormal = Dot4(weights[j], weights[j+1], weights[j+2], weights[j+3], 339 | normals[sampleIds[j]], normals[sampleIds[j+1]], 340 | normals[sampleIds[j+2]], normals[sampleIds[j+3]]); 341 | normalV = _mm256_add_pd(tempNormal, normalV); 342 | } 343 | 344 | _mm256_store_pd(alignedStorage, originV); 345 | origin.x = alignedStorage[0]; 346 | origin.y = alignedStorage[1]; 347 | origin.z = alignedStorage[2]; 348 | _mm256_store_pd(alignedStorage, normalV); 349 | normal.x = alignedStorage[0]; 350 | normal.y = alignedStorage[1]; 351 | normal.z = alignedStorage[2]; 352 | 353 | // Calculate the up vector 354 | const MPoint& pt1 = points[triangleVertices[0]]; 355 | const MPoint& pt2 = points[triangleVertices[1]]; 356 | __m256d p1 = _mm256_set_pd(pt1.w, pt1.z, pt1.y, pt1.x); 357 | __m256d p2 = _mm256_set_pd(pt2.w, pt2.z, pt2.y, pt2.x); 358 | p1 = _mm256_add_pd(p1, p2); 359 | __m256d half = _mm256_set_pd(0.5, 0.5, 0.5, 0.5); 360 | p1 = _mm256_mul_pd(p1, half); 361 | __m256d upV = _mm256_sub_pd(p1, originV); 362 | _mm256_store_pd(alignedStorage, upV); 363 | up.x = alignedStorage[0]; 364 | up.y = alignedStorage[1]; 365 | up.z = alignedStorage[2]; 366 | #else 367 | MVector hitNormal; 368 | // Create the barycentric point and normal. 369 | for (int i = 0; i < 3; ++i) { 370 | origin += points[triangleVertices[i]] * coords[i]; 371 | hitNormal += MVector(normals[triangleVertices[i]]) * coords[i]; 372 | } 373 | // Use crawl data to calculate normal 374 | normal = hitNormal * weights[hitIndex]; 375 | for (unsigned int j = 0; j < hitIndex; j++) { 376 | normal += MVector(normals[sampleIds[j]]) * weights[j]; 377 | } 378 | 379 | // Calculate the up vector 380 | // The triangle vertices are sorted by decreasing barycentric coordinates so the first two are 381 | // the two closest vertices in the triangle. 382 | up = ((points[triangleVertices[0]] + points[triangleVertices[1]]) * 0.5) - origin; 383 | #endif 384 | normal.normalize(); 385 | GetValidUp(weights, points, sampleIds, origin, normal, up); 386 | } 387 | 388 | 389 | void GetValidUp(const MDoubleArray& weights, const MPointArray& points, 390 | const MIntArray& sampleIds, const MPoint& origin, const MVector& normal, 391 | MVector& up) { 392 | MVector unitUp = up.normal(); 393 | // Adjust up if it's parallel to normal or if it's zero length 394 | if (std::abs((unitUp * normal) - 1.0) < 0.001 || up.length() < 0.0001) { 395 | for (unsigned int j = 0; j < weights.length()-1; ++j) { 396 | up -= (points[sampleIds[j]] - origin) * weights[j]; 397 | unitUp = up.normal(); 398 | if (std::abs((unitUp * normal) - 1.0) > 0.001 && up.length() > 0.0001) { 399 | // If the up and normal vectors are no longer parallel and the up vector has a length, 400 | // then we are good to go. 401 | break; 402 | } 403 | } 404 | up.normalize(); 405 | } else { 406 | up = unitUp; 407 | } 408 | } 409 | -------------------------------------------------------------------------------- /src/cvWrapCmd.cpp: -------------------------------------------------------------------------------- 1 | #include "cvWrapCmd.h" 2 | #include "cvWrapDeformer.h" 3 | #include "bindingio.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #define PROGRESS_STEP 100 23 | #define TASK_COUNT 32 24 | 25 | /** 26 | A version number used to support future updates to the binary wrap binding file. 27 | */ 28 | const float kWrapFileVersion = 1.0f; 29 | 30 | const char* CVWrapCmd::kName = "cvWrap"; 31 | const char* CVWrapCmd::kNameFlagShort = "-n"; 32 | const char* CVWrapCmd::kNameFlagLong = "-name"; 33 | const char* CVWrapCmd::kRadiusFlagShort = "-r"; 34 | const char* CVWrapCmd::kRadiusFlagLong = "-radius"; 35 | const char* CVWrapCmd::kNewBindMeshFlagShort = "-nbm"; 36 | const char* CVWrapCmd::kNewBindMeshFlagLong = "-newBindMesh"; 37 | const char* CVWrapCmd::kExportFlagShort = "-ex"; 38 | const char* CVWrapCmd::kExportFlagLong = "-export"; 39 | const char* CVWrapCmd::kImportFlagShort = "-im"; 40 | const char* CVWrapCmd::kImportFlagLong = "-import"; 41 | const char* CVWrapCmd::kBindingFlagShort = "-b"; 42 | const char* CVWrapCmd::kBindingFlagLong = "-binding"; 43 | const char* CVWrapCmd::kRebindFlagShort = "-rb"; 44 | const char* CVWrapCmd::kRebindFlagLong = "-rebind"; 45 | const char* CVWrapCmd::kHelpFlagShort = "-h"; 46 | const char* CVWrapCmd::kHelpFlagLong = "-help"; 47 | 48 | /** 49 | Displays command instructions. 50 | */ 51 | void DisplayHelp() { 52 | MString help; 53 | help += "Flags:\n"; 54 | help += "-name (-n): String Name of the wrap node to create.\n"; 55 | help += "-radius (-r): Double Sample radius. Default is 0.1. The greater the radius,\n"; 56 | help += " the smoother the deformation but slower performance.\n"; 57 | help += "-newBindMesh (-nbm) N/A Creates a new bind mesh, otherwise the existing bind mesh will be used.\n"; 58 | help += "-export (-ex): String Path to a file to export the binding to.\n"; 59 | help += "-import (-im): String Path to a file to import the binding from.\n"; 60 | help += "-binding (-b): String Path to a file to import the binding from on creation.\n"; 61 | help += "-rebind (-rb): String The name of the wrap node we are rebinding.\n"; 62 | help += "-help (-h) N/A Display this text.\n"; 63 | MGlobal::displayInfo(help); 64 | } 65 | 66 | 67 | CVWrapCmd::CVWrapCmd() 68 | : radius_(0.1), 69 | name_("cvWrap#"), 70 | command_(kCommandCreate), 71 | useBinding_(false), 72 | newBindMesh_(false) { 73 | } 74 | 75 | 76 | MSyntax CVWrapCmd::newSyntax() { 77 | MSyntax syntax; 78 | syntax.addFlag(kNameFlagShort, kNameFlagLong, MSyntax::kString); 79 | syntax.addFlag(kRadiusFlagShort, kRadiusFlagLong, MSyntax::kDouble); 80 | syntax.addFlag(kNewBindMeshFlagShort, kNewBindMeshFlagLong); 81 | syntax.addFlag(kExportFlagShort, kExportFlagLong, MSyntax::kString); 82 | syntax.addFlag(kImportFlagShort, kImportFlagLong, MSyntax::kString); 83 | syntax.addFlag(kBindingFlagShort, kBindingFlagLong, MSyntax::kString); 84 | syntax.addFlag(kRebindFlagShort, kRebindFlagLong, MSyntax::kString); 85 | syntax.addFlag(kHelpFlagShort, kHelpFlagLong); 86 | syntax.setObjectType(MSyntax::kSelectionList, 0, 255); 87 | syntax.useSelectionAsDefault(true); 88 | return syntax; 89 | } 90 | 91 | 92 | void* CVWrapCmd::creator() { 93 | return new CVWrapCmd; 94 | } 95 | 96 | 97 | bool CVWrapCmd::isUndoable() const { 98 | return command_ == kCommandCreate; // Only creation will be undoable 99 | } 100 | 101 | 102 | MStatus CVWrapCmd::doIt(const MArgList& args) { 103 | MStatus status; 104 | 105 | status = GatherCommandArguments(args); 106 | CHECK_MSTATUS_AND_RETURN_IT(status); 107 | 108 | if (command_ == kCommandImport || command_ == kCommandExport) { 109 | // In import/export mode, get the selected wrap deformer node so we can read/write 110 | // data from it. 111 | status = selectionList_.getDependNode(0, oWrapNode_); 112 | CHECK_MSTATUS_AND_RETURN_IT(status); 113 | MFnDependencyNode fnNode(oWrapNode_); 114 | if (fnNode.typeId() != CVWrap::id) { 115 | MGlobal::displayError("No wrap node specified."); 116 | return MS::kFailure; 117 | } 118 | } else if (command_ == kCommandRebind) { 119 | status = GetGeometryPaths(); 120 | CHECK_MSTATUS_AND_RETURN_IT(status); 121 | status = Rebind(); 122 | CHECK_MSTATUS_AND_RETURN_IT(status); 123 | } else { 124 | // Otherwise get the driver and driven geometry paths. 125 | status = GetGeometryPaths(); 126 | CHECK_MSTATUS_AND_RETURN_IT(status); 127 | 128 | // Add the cvWrap creation command to the modifier. 129 | MString command = "deformer -type cvWrap -n \"" + name_ + "\""; 130 | for (unsigned int i = 0; i < pathDriven_.length(); ++i) { 131 | MFnDagNode fnDriven(pathDriven_[i]); 132 | command += " " + fnDriven.partialPathName(); 133 | } 134 | status = dgMod_.commandToExecute(command); 135 | CHECK_MSTATUS_AND_RETURN_IT(status); 136 | } 137 | 138 | return redoIt(); 139 | } 140 | 141 | 142 | MStatus CVWrapCmd::GatherCommandArguments(const MArgList& args) { 143 | MStatus status; 144 | MArgDatabase argData(syntax(), args); 145 | argData.getObjects(selectionList_); 146 | if (argData.isFlagSet(kHelpFlagShort)) { 147 | command_ = kCommandHelp; 148 | DisplayHelp(); 149 | return MS::kSuccess; 150 | } else if (argData.isFlagSet(kExportFlagShort)) { 151 | command_ = kCommandExport; 152 | filePath_ = argData.flagArgumentString(kExportFlagShort, 0, &status); 153 | CHECK_MSTATUS_AND_RETURN_IT(status); 154 | } else if (argData.isFlagSet(kImportFlagShort)) { 155 | command_ = kCommandImport; 156 | filePath_ = argData.flagArgumentString(kImportFlagShort, 0, &status); 157 | CHECK_MSTATUS_AND_RETURN_IT(status); 158 | } 159 | newBindMesh_ = argData.isFlagSet(kNewBindMeshFlagShort); 160 | if (argData.isFlagSet(kRadiusFlagShort)) { 161 | radius_ = argData.flagArgumentDouble(kRadiusFlagShort, 0, &status); 162 | CHECK_MSTATUS_AND_RETURN_IT(status); 163 | // Make sure radius is positive 164 | if (radius_ <= 0.0) { 165 | radius_ = 0.001; 166 | } 167 | } 168 | if (argData.isFlagSet(kNameFlagShort)) { 169 | name_ = argData.flagArgumentString(kNameFlagShort, 0, &status); 170 | CHECK_MSTATUS_AND_RETURN_IT(status); 171 | } 172 | if (argData.isFlagSet(kBindingFlagShort)) { 173 | useBinding_ = true; 174 | filePath_ = argData.flagArgumentString(kBindingFlagShort, 0, &status); 175 | CHECK_MSTATUS_AND_RETURN_IT(status); 176 | } 177 | if (argData.isFlagSet(kRebindFlagShort)) { 178 | command_ = kCommandRebind; 179 | // Get the specified wrap node to rebind. 180 | MString wrapNode = argData.flagArgumentString(kRebindFlagShort, 0, &status); 181 | MSelectionList slist; 182 | status = slist.add(wrapNode); 183 | CHECK_MSTATUS_AND_RETURN_IT(status); 184 | status = slist.getDependNode(0, oWrapNode_); 185 | CHECK_MSTATUS_AND_RETURN_IT(status); 186 | MFnDependencyNode fnNode(oWrapNode_, &status); 187 | CHECK_MSTATUS_AND_RETURN_IT(status); 188 | if (fnNode.typeId() != CVWrap::id) { 189 | MGlobal::displayError(fnNode.name() + " is not a cvWrap node."); 190 | return MS::kFailure; 191 | } 192 | } 193 | return MS::kSuccess; 194 | } 195 | 196 | 197 | MStatus CVWrapCmd::GetGeometryPaths() { 198 | MStatus status; 199 | // The driver is selected last 200 | status = selectionList_.getDagPath(selectionList_.length() - 1, pathDriver_, driverComponents_); 201 | CHECK_MSTATUS_AND_RETURN_IT(status); 202 | status = GetShapeNode(pathDriver_); 203 | // The driver must be a mesh for this specific algorithm. 204 | if (!pathDriver_.hasFn(MFn::kMesh)) { 205 | MGlobal::displayError("cvWrap driver must be a mesh."); 206 | return MS::kFailure; 207 | } 208 | 209 | MItSelectionList iter(selectionList_); 210 | CHECK_MSTATUS_AND_RETURN_IT(status); 211 | pathDriven_.clear(); 212 | drivenComponents_.clear(); 213 | for (unsigned int i = 0; i < selectionList_.length() - 1; ++i, iter.next()) { 214 | MDagPath path; 215 | MObject component; 216 | iter.getDagPath(path, component); 217 | status = GetShapeNode(path); 218 | CHECK_MSTATUS_AND_RETURN_IT(status); 219 | pathDriven_.append(path); 220 | drivenComponents_.append(component); 221 | } 222 | 223 | return MS::kSuccess; 224 | } 225 | 226 | 227 | MStatus CVWrapCmd::redoIt() { 228 | MStatus status; 229 | if (command_ == kCommandImport) { 230 | std::ifstream in(filePath_.asChar(), ios::binary); 231 | if (!in.is_open()) { 232 | MGlobal::displayInfo("Unable to open file for importing."); 233 | CHECK_MSTATUS_AND_RETURN_IT(MS::kFailure); 234 | } 235 | BindingIO exporter; 236 | status = exporter.ImportBinding(in, oWrapNode_); 237 | in.close(); 238 | CHECK_MSTATUS_AND_RETURN_IT(status); 239 | return MS::kSuccess; 240 | } else if (command_ == kCommandExport) { 241 | std::ofstream out(filePath_.asChar(), ios::binary); 242 | if (!out.is_open()) { 243 | MGlobal::displayError("Unable to open file for writing."); 244 | return MS::kFailure; 245 | } 246 | BindingIO exporter; 247 | status = exporter.ExportBinding(out, oWrapNode_); 248 | out.close(); 249 | CHECK_MSTATUS_AND_RETURN_IT(status); 250 | return MS::kSuccess; 251 | } else if (command_ == kCommandRebind) { 252 | status = dgMod_.doIt(); 253 | CHECK_MSTATUS_AND_RETURN_IT(status); 254 | return MS::kSuccess; 255 | } else if (command_ == kCommandCreate) { 256 | status = CreateWrapDeformer(); 257 | CHECK_MSTATUS_AND_RETURN_IT(status); 258 | return MS::kSuccess; 259 | } 260 | return MS::kFailure; 261 | } 262 | 263 | 264 | MStatus CVWrapCmd::CreateWrapDeformer() { 265 | MStatus status; 266 | // Create the deformer 267 | status = dgMod_.doIt(); 268 | CHECK_MSTATUS_AND_RETURN_IT(status); 269 | // Reacquire the paths because on referenced geo, a new driven path is created (the ShapeDeformed). 270 | status = GetGeometryPaths(); 271 | CHECK_MSTATUS_AND_RETURN_IT(status); 272 | // Get the created wrap deformer node. 273 | status = GetLatestWrapNode(); 274 | CHECK_MSTATUS_AND_RETURN_IT(status); 275 | 276 | MFnDependencyNode fnNode(oWrapNode_, &status); 277 | setResult(fnNode.name()); 278 | CHECK_MSTATUS_AND_RETURN_IT(status); 279 | 280 | // Create a bind mesh so we can run rebind commands. We need a mesh at the state of the 281 | // initial binding in order to properly calculate rebinding information. We can't use 282 | // the intermediate mesh for rebinding because we may not be binding at the rest pose. 283 | // Check if this driver already has a bind mesh. 284 | MDagPath pathBindMesh; 285 | status = GetExistingBindMesh(pathBindMesh); 286 | CHECK_MSTATUS_AND_RETURN_IT(status); 287 | if (newBindMesh_ || !pathBindMesh.isValid()) { 288 | // No bind mesh exists or the user wants to force create a new one. 289 | status = CreateBindMesh(pathBindMesh); 290 | CHECK_MSTATUS_AND_RETURN_IT(status); 291 | } 292 | status = ConnectBindMesh(pathBindMesh); 293 | CHECK_MSTATUS_AND_RETURN_IT(status); 294 | 295 | if (useBinding_) { 296 | // Import a pre-existing binding. 297 | std::ifstream in(filePath_.asChar(), ios::binary); 298 | if (!in.is_open()) { 299 | MGlobal::displayInfo("Unable to open file for importing."); 300 | CHECK_MSTATUS_AND_RETURN_IT(MS::kFailure); 301 | } 302 | BindingIO exporter; 303 | status = exporter.ImportBinding(in, oWrapNode_); 304 | in.close(); 305 | CHECK_MSTATUS_AND_RETURN_IT(status); 306 | } else { 307 | MDGModifier dgMod; 308 | BindData bindData; 309 | status = CalculateBinding(pathBindMesh, bindData, dgMod); 310 | CHECK_MSTATUS_AND_RETURN_IT(status); 311 | status = dgMod.doIt(); 312 | CHECK_MSTATUS_AND_RETURN_IT(status); 313 | } 314 | 315 | // Connect the driver mesh to the wrap deformer. 316 | MFnDagNode fnDriver(pathDriver_); 317 | MPlug plugDriverMesh = fnDriver.findPlug("worldMesh", false, &status); 318 | CHECK_MSTATUS_AND_RETURN_IT(status); 319 | status = plugDriverMesh.selectAncestorLogicalIndex(0, plugDriverMesh.attribute()); 320 | CHECK_MSTATUS_AND_RETURN_IT(status); 321 | MPlug plugDriverGeo(oWrapNode_, CVWrap::aDriverGeo); 322 | MDGModifier dgMod; 323 | dgMod.connect(plugDriverMesh, plugDriverGeo); 324 | status = dgMod.doIt(); 325 | CHECK_MSTATUS_AND_RETURN_IT(status); 326 | 327 | return MS::kSuccess; 328 | } 329 | 330 | 331 | MStatus CVWrapCmd::GetLatestWrapNode() { 332 | MStatus status; 333 | MObject oDriven = pathDriven_[0].node(); 334 | 335 | // Since we use MDGModifier to execute the deformer command, we can't get 336 | // the created deformer node, so we need to find it in the deformation chain. 337 | MItDependencyGraph itDG(oDriven, 338 | MFn::kGeometryFilt, 339 | MItDependencyGraph::kUpstream, 340 | MItDependencyGraph::kDepthFirst, 341 | MItDependencyGraph::kNodeLevel, 342 | &status); 343 | CHECK_MSTATUS_AND_RETURN_IT(status); 344 | MObject oDeformerNode; 345 | for (; !itDG.isDone(); itDG.next()) { 346 | oDeformerNode = itDG.currentItem(); 347 | MFnDependencyNode fnNode(oDeformerNode, &status); 348 | CHECK_MSTATUS_AND_RETURN_IT(status); 349 | if (fnNode.typeId() == CVWrap::id) { 350 | oWrapNode_ = oDeformerNode; 351 | return MS::kSuccess; 352 | } 353 | } 354 | return MS::kFailure; 355 | } 356 | 357 | 358 | MStatus CVWrapCmd::CreateBindMesh(MDagPath& pathBindMesh) { 359 | MStatus status; 360 | MStringArray duplicate; 361 | MFnDependencyNode fnWrap(oWrapNode_, &status); 362 | CHECK_MSTATUS_AND_RETURN_IT(status); 363 | MFnDagNode fnDriver(pathDriver_); 364 | 365 | // Calling mesh.duplicate() can give incorrect results due to tweaks and such. 366 | // We are doing the duplicate here rather than the MDGModifier because we need the name 367 | // of the duplicated geometry and it would not be reliable to do it from the modifier. 368 | MGlobal::executeCommand("duplicate -rr -n " + fnWrap.name() + "Base " + fnDriver.partialPathName(), duplicate); 369 | status = GetDagPath(duplicate[0], pathBindMesh); 370 | CHECK_MSTATUS_AND_RETURN_IT(status); 371 | status = DeleteIntermediateObjects(pathBindMesh); 372 | CHECK_MSTATUS_AND_RETURN_IT(status); 373 | bindMeshes_.append(duplicate[0]); 374 | 375 | // Hide the duplicate 376 | MFnDagNode fnBindMesh(pathBindMesh, &status); 377 | CHECK_MSTATUS_AND_RETURN_IT(status); 378 | MPlug plug = fnBindMesh.findPlug("visibility", false, &status); 379 | CHECK_MSTATUS_AND_RETURN_IT(status); 380 | status = plug.setBool(false); 381 | CHECK_MSTATUS_AND_RETURN_IT(status); 382 | 383 | return MS::kSuccess; 384 | } 385 | 386 | 387 | MStatus CVWrapCmd::ConnectBindMesh(MDagPath& pathBindMesh) { 388 | MStatus status; 389 | // Connect the bind mesh to the wrap node 390 | status = GetShapeNode(pathBindMesh); 391 | CHECK_MSTATUS_AND_RETURN_IT(status); 392 | MFnDagNode fnBindMeshShape(pathBindMesh, &status); 393 | CHECK_MSTATUS_AND_RETURN_IT(status); 394 | MPlug plugBindMessage = fnBindMeshShape.findPlug("message", false, &status); 395 | CHECK_MSTATUS_AND_RETURN_IT(status); 396 | MPlug plugBindMesh(oWrapNode_, CVWrap::aBindDriverGeo); 397 | MDGModifier dgMod; 398 | dgMod.connect(plugBindMessage, plugBindMesh); 399 | status = dgMod.doIt(); 400 | CHECK_MSTATUS_AND_RETURN_IT(status); 401 | return MS::kSuccess; 402 | } 403 | 404 | 405 | MStatus CVWrapCmd::CalculateBinding(MDagPath& pathBindMesh, BindData& bindData, 406 | MDGModifier& dgMod) { 407 | MStatus status; 408 | bindData.radius = radius_; 409 | 410 | // Store the bind mesh information. 411 | // Pre-gather the data from Maya so we can multithread the binding process 412 | bindData.driverMatrix = pathBindMesh.inclusiveMatrix(); 413 | MObject oBindMesh = pathBindMesh.node(); 414 | status = bindData.intersector.create(oBindMesh, bindData.driverMatrix); 415 | CHECK_MSTATUS_AND_RETURN_IT(status); 416 | // We need the adjacency of each vertex in order to crawl the mesh. 417 | status = GetAdjacency(pathBindMesh, bindData.adjacency); 418 | CHECK_MSTATUS_AND_RETURN_IT(status); 419 | MFnMesh fnBindMesh(pathBindMesh, &status); 420 | CHECK_MSTATUS_AND_RETURN_IT(status); 421 | fnBindMesh.getPoints(bindData.driverPoints, MSpace::kWorld); 422 | fnBindMesh.getVertexNormals(false, bindData.driverNormals, MSpace::kWorld); 423 | bindData.perFaceVertices.resize(fnBindMesh.numPolygons()); 424 | bindData.perFaceTriangleVertices.resize(fnBindMesh.numPolygons()); 425 | MIntArray vertexCount, vertexList, triangleCounts, triangleVertices; 426 | status = fnBindMesh.getVertices(vertexCount, vertexList); 427 | CHECK_MSTATUS_AND_RETURN_IT(status); 428 | status = fnBindMesh.getTriangles(triangleCounts, triangleVertices); 429 | CHECK_MSTATUS_AND_RETURN_IT(status); 430 | for (unsigned int faceId = 0, iter = 0, triIter = 0; faceId < vertexCount.length(); ++faceId) { 431 | bindData.perFaceVertices[faceId].clear(); 432 | for (int i = 0; i < vertexCount[faceId]; ++i, ++iter) { 433 | bindData.perFaceVertices[faceId].append(vertexList[iter]); 434 | } 435 | bindData.perFaceTriangleVertices[faceId].resize(triangleCounts[faceId]); 436 | for (int triId = 0; triId < triangleCounts[faceId]; ++triId) { 437 | bindData.perFaceTriangleVertices[faceId][triId].setLength(3); 438 | bindData.perFaceTriangleVertices[faceId][triId][0] = triangleVertices[triIter++]; 439 | bindData.perFaceTriangleVertices[faceId][triId][1] = triangleVertices[triIter++]; 440 | bindData.perFaceTriangleVertices[faceId][triId][2] = triangleVertices[triIter++]; 441 | } 442 | } 443 | 444 | // Calculate the binding for each deformed geometry 445 | MPlug plugBindData(oWrapNode_, CVWrap::aBindData); 446 | MFnMatrixData fnMatrixData; 447 | for (unsigned int geomIndex = 0; geomIndex < pathDriven_.length(); ++geomIndex) { 448 | // Get the plugs to the binding attributes for this geometry 449 | MPlug plugBind = plugBindData.elementByLogicalIndex(geomIndex, &status); 450 | CHECK_MSTATUS_AND_RETURN_IT(status); 451 | MPlug plugSampleWeights = plugBind.child(CVWrap::aSampleWeights, &status); 452 | CHECK_MSTATUS_AND_RETURN_IT(status); 453 | MPlug plugSampleVerts = plugBind.child(CVWrap::aSampleComponents, &status); 454 | CHECK_MSTATUS_AND_RETURN_IT(status); 455 | MPlug plugSampleBindMatrix = plugBind.child(CVWrap::aBindMatrix, &status); 456 | CHECK_MSTATUS_AND_RETURN_IT(status); 457 | MPlug plugTriangleVerts = plugBind.child(CVWrap::aTriangleVerts, &status); 458 | CHECK_MSTATUS_AND_RETURN_IT(status); 459 | MPlug plugBarycentricWeights = plugBind.child(CVWrap::aBarycentricWeights, &status); 460 | CHECK_MSTATUS_AND_RETURN_IT(status); 461 | 462 | // Use the intermediate object for the binding. This assumes the intermediate object 463 | // has the same component count as the displayed shape. 464 | MDagPath pathDriven(pathDriven_[geomIndex]); 465 | status = GetShapeNode(pathDriven, true); 466 | if (MFAIL(status)) { 467 | pathDriven = pathDriven_[geomIndex]; 468 | } 469 | MItGeometry itGeo(pathDriven, drivenComponents_[geomIndex], &status); 470 | CHECK_MSTATUS_AND_RETURN_IT(status); 471 | int geoCount = itGeo.count(); 472 | 473 | status = itGeo.allPositions(bindData.inputPoints, MSpace::kWorld); 474 | CHECK_MSTATUS_AND_RETURN_IT(status); 475 | bindData.sampleIds.resize(itGeo.count()); 476 | bindData.weights.resize(itGeo.count()); 477 | bindData.bindMatrices.setLength(itGeo.count()); 478 | bindData.coords.resize(itGeo.count()); 479 | bindData.triangleVertices.resize(itGeo.count()); 480 | 481 | // Send off the threads to calculate the binding. 482 | ThreadData threadData[TASK_COUNT]; 483 | CreateThreadData(TASK_COUNT, itGeo.count(), &bindData, threadData); 484 | MThreadPool::init(); 485 | MThreadPool::newParallelRegion(CreateTasks, (void *)threadData); 486 | MThreadPool::release(); 487 | 488 | for (int ii = 0; !itGeo.isDone(); itGeo.next(), ++ii) { 489 | // Store all the binding data for this component 490 | // Note for nurbs surfaces the indices may not be continuous. 491 | int logicalIndex = itGeo.index(); 492 | // Store sample vert ids. 493 | MFnIntArrayData fnIntData; 494 | MObject oIntData = fnIntData.create(bindData.sampleIds[ii], &status); 495 | CHECK_MSTATUS_AND_RETURN_IT(status); 496 | MPlug plugSampleVertsElement = plugSampleVerts.elementByLogicalIndex(logicalIndex, &status); 497 | CHECK_MSTATUS_AND_RETURN_IT(status); 498 | status = dgMod.newPlugValue(plugSampleVertsElement, oIntData); 499 | CHECK_MSTATUS_AND_RETURN_IT(status); 500 | 501 | // Store sample weights 502 | MFnDoubleArrayData fnDoubleData; 503 | MObject oDoubleData = fnDoubleData.create(bindData.weights[ii], &status); 504 | assert(bindData.weights[ii].length() > 0); 505 | CHECK_MSTATUS_AND_RETURN_IT(status); 506 | MPlug plugSampleWeightsElement = plugSampleWeights.elementByLogicalIndex(logicalIndex, 507 | &status); 508 | CHECK_MSTATUS_AND_RETURN_IT(status); 509 | status = dgMod.newPlugValue(plugSampleWeightsElement, oDoubleData); 510 | CHECK_MSTATUS_AND_RETURN_IT(status); 511 | 512 | // Store bind matrix 513 | MObject oMatrixData = fnMatrixData.create(bindData.bindMatrices[ii], &status); 514 | CHECK_MSTATUS_AND_RETURN_IT(status); 515 | MPlug plugSampleBindMatrixElement = plugSampleBindMatrix.elementByLogicalIndex(logicalIndex, 516 | &status); 517 | CHECK_MSTATUS_AND_RETURN_IT(status); 518 | status = dgMod.newPlugValue(plugSampleBindMatrixElement, oMatrixData); 519 | CHECK_MSTATUS_AND_RETURN_IT(status); 520 | 521 | // Store triangle vertices 522 | MFnNumericData fnNumericData; 523 | MObject oNumericData = fnNumericData.create(MFnNumericData::k3Int, &status); 524 | CHECK_MSTATUS_AND_RETURN_IT(status); 525 | status = fnNumericData.setData3Int(bindData.triangleVertices[ii][0], 526 | bindData.triangleVertices[ii][1], 527 | bindData.triangleVertices[ii][2]); 528 | CHECK_MSTATUS_AND_RETURN_IT(status); 529 | MPlug plugTriangleVertsElement = plugTriangleVerts.elementByLogicalIndex(logicalIndex, 530 | &status); 531 | CHECK_MSTATUS_AND_RETURN_IT(status); 532 | status = dgMod.newPlugValue(plugTriangleVertsElement, oNumericData); 533 | CHECK_MSTATUS_AND_RETURN_IT(status); 534 | 535 | // Store barycentric coordinates 536 | oNumericData = fnNumericData.create(MFnNumericData::k3Float, &status); 537 | CHECK_MSTATUS_AND_RETURN_IT(status); 538 | status = fnNumericData.setData3Float(bindData.coords[ii][0], bindData.coords[ii][1], 539 | bindData.coords[ii][2]); 540 | CHECK_MSTATUS_AND_RETURN_IT(status); 541 | MPlug plugBarycentricWeightsElement = plugBarycentricWeights.elementByLogicalIndex( 542 | logicalIndex, &status); 543 | CHECK_MSTATUS_AND_RETURN_IT(status); 544 | status = dgMod.newPlugValue(plugBarycentricWeightsElement, oNumericData); 545 | CHECK_MSTATUS_AND_RETURN_IT(status); 546 | } 547 | } 548 | return MS::kSuccess; 549 | } 550 | 551 | 552 | void CVWrapCmd::CreateTasks(void *data, MThreadRootTask *pRoot) { 553 | ThreadData* threadData = static_cast*>(data); 554 | 555 | if (threadData) { 556 | int numTasks = threadData[0].numTasks; 557 | for(int i = 0; i < numTasks; i++) { 558 | MThreadPool::createTask(CalculateBindingTask, (void *)&threadData[i], pRoot); 559 | } 560 | MThreadPool::executeAndJoin(pRoot); 561 | } 562 | } 563 | 564 | bool SortCoords(std::pair lhs, std::pair rhs) { 565 | return (lhs.second > rhs.second); 566 | } 567 | 568 | 569 | MThreadRetVal CVWrapCmd::CalculateBindingTask(void *pParam) { 570 | ThreadData* pThreadData = static_cast*>(pParam); 571 | double*& alignedStorage = pThreadData->alignedStorage; 572 | BindData* pData = pThreadData->pData; 573 | MMeshIntersector& intersector = pData->intersector; 574 | MMeshIntersector& subsetIntersector = pData->subsetIntersector; 575 | MPointArray& inputPoints = pData->inputPoints; 576 | MPointArray& driverPoints = pData->driverPoints; 577 | MFloatVectorArray& driverNormals = pData->driverNormals; 578 | std::vector >& adjacency = pData->adjacency; 579 | std::vector& sampleIds = pData->sampleIds; 580 | std::vector& weights = pData->weights; 581 | std::vector& coords = pData->coords; 582 | std::vector& triangleVertices = pData->triangleVertices; 583 | MMatrixArray& bindMatrices = pData->bindMatrices; 584 | 585 | double radius = pData->radius; 586 | 587 | MMatrix& driverMatrix = pData->driverMatrix; 588 | std::vector& perFaceVertices = pData->perFaceVertices; 589 | std::vector >& perFaceTriangleVertices = pData->perFaceTriangleVertices; 590 | 591 | unsigned int taskStart = pThreadData->start; 592 | unsigned int taskEnd = pThreadData->end; 593 | 594 | // Pre-allocate the aligned storage for intrinsics calculation so we are not dynamically allocating 595 | // memory in the loop. 596 | std::vector > sortedCoords(3); 597 | for (unsigned int i = taskStart; i < taskEnd; ++i) { 598 | if (i >= inputPoints.length()) { 599 | break; 600 | } 601 | // We need to calculate a bind matrix for each component. 602 | // The closest point will be the origin of the coordinate system. 603 | // The weighted normal of the vertices in the sample radius will be one axis. 604 | // The weight vector from the closest point to the sample vertices will be the other axis. 605 | 606 | MPoint inputPoint = inputPoints[i]; 607 | MPointOnMesh pointOnMesh; 608 | if (subsetIntersector.isCreated()) { 609 | // If we are rebinding, limit the closest point to the subset. 610 | subsetIntersector.getClosestPoint(inputPoint, pointOnMesh); 611 | inputPoint = MPoint(pointOnMesh.getPoint()) * driverMatrix; 612 | } 613 | 614 | intersector.getClosestPoint(inputPoint, pointOnMesh); 615 | int faceId = pointOnMesh.faceIndex(); 616 | int triangleId = pointOnMesh.triangleIndex(); 617 | 618 | // Put point in world space so we can calculate the proper bind matrix. 619 | MPoint closestPoint = MPoint(pointOnMesh.getPoint()) * driverMatrix; 620 | 621 | // Get barycentric coordinates of closestPoint 622 | triangleVertices[i] = perFaceTriangleVertices[faceId][triangleId]; 623 | GetBarycentricCoordinates(closestPoint, driverPoints[triangleVertices[i][0]], 624 | driverPoints[triangleVertices[i][1]], 625 | driverPoints[triangleVertices[i][2]], 626 | coords[i]); 627 | 628 | // Sort coords highest to lowest so we can easility calculate the up vector 629 | for (int j = 0; j < 3; ++j) { 630 | sortedCoords[j] = std::pair(triangleVertices[i][j], coords[i][j]); 631 | } 632 | std::sort(sortedCoords.begin(), sortedCoords.end(), SortCoords); 633 | for (int j = 0; j < 3; ++j) { 634 | triangleVertices[i][j] = sortedCoords[j].first; 635 | coords[i][j] = sortedCoords[j].second; 636 | } 637 | 638 | // Get vertices of closest face so we can crawl out from them. 639 | MIntArray& vertexList = perFaceVertices[faceId]; 640 | 641 | // Crawl the surface to find all the vertices within the sample radius. 642 | std::map distances; 643 | CrawlSurface(closestPoint, vertexList, driverPoints, radius, adjacency, distances); 644 | 645 | // Calculate the weight values per sampled vertex 646 | CalculateSampleWeights(distances, radius, sampleIds[i], weights[i]); 647 | 648 | // Get the components that form the orthonormal basis. 649 | MPoint origin; 650 | MVector up; 651 | MVector normal; 652 | CalculateBasisComponents(weights[i], coords[i], triangleVertices[i], driverPoints, 653 | driverNormals, sampleIds[i], alignedStorage, origin, up, normal); 654 | CreateMatrix(origin, normal, up, bindMatrices[i]); 655 | bindMatrices[i] = bindMatrices[i].inverse(); 656 | } 657 | return 0; 658 | } 659 | 660 | 661 | MStatus CVWrapCmd::GetExistingBindMesh(MDagPath &pathBindMesh) { 662 | MStatus status; 663 | MObject oDriver = pathDriver_.node(); 664 | MFnDependencyNode fnDriver(oDriver, &status); 665 | CHECK_MSTATUS_AND_RETURN_IT(status); 666 | 667 | // We'll find the bind mesh associated with the driver mesh by traversing the mesh connections 668 | // through the cvWrap node. 669 | MPlug plugOutGeom = fnDriver.findPlug("worldMesh", false, &status); 670 | CHECK_MSTATUS_AND_RETURN_IT(status); 671 | status = plugOutGeom.selectAncestorLogicalIndex(0, plugOutGeom.attribute()); 672 | CHECK_MSTATUS_AND_RETURN_IT(status); 673 | MPlugArray geomPlugs; 674 | plugOutGeom.connectedTo(geomPlugs, false, true); 675 | for (unsigned int i = 0; i < geomPlugs.length(); i++) { 676 | // First iterate through the outMesh connections to find a cvWrap node. 677 | MObject oThisNode = geomPlugs[i].node(); 678 | MFnDependencyNode fnNode(oThisNode); 679 | if (fnNode.typeId() == CVWrap::id) { 680 | status = GetBindMesh(oThisNode, pathBindMesh); 681 | CHECK_MSTATUS_AND_RETURN_IT(status); 682 | return MS::kSuccess; 683 | } 684 | } 685 | return MS::kSuccess; 686 | } 687 | 688 | 689 | MStatus CVWrapCmd::Rebind() { 690 | MStatus status; 691 | 692 | // Create bind mesh based off of specified faces 693 | MDagPath pathDriverSubset; 694 | status = CreateRebindSubsetMesh(pathDriverSubset); 695 | CHECK_MSTATUS_AND_RETURN_IT(status); 696 | 697 | // Initialize the subset intersector to enable the rebind during the threaded calculation. 698 | BindData bindData; 699 | MObject oBindSubsetMesh = pathDriverSubset.node(); 700 | status = bindData.subsetIntersector.create(oBindSubsetMesh, pathDriverSubset.inclusiveMatrix()); 701 | CHECK_MSTATUS_AND_RETURN_IT(status); 702 | 703 | MDagPath pathBindMesh; 704 | status = GetBindMesh(oWrapNode_, pathBindMesh); 705 | CHECK_MSTATUS_AND_RETURN_IT(status); 706 | 707 | status = CalculateBinding(pathBindMesh, bindData, dgMod_); 708 | CHECK_MSTATUS_AND_RETURN_IT(status); 709 | 710 | // Delete the subset mesh since we don't need it anymore 711 | pathDriverSubset.pop(); 712 | status = MGlobal::executeCommand("delete " + pathDriverSubset.partialPathName()); 713 | CHECK_MSTATUS_AND_RETURN_IT(status); 714 | 715 | return MS::kSuccess; 716 | } 717 | 718 | 719 | MStatus CVWrapCmd::GetBindMesh(MObject& oWrapNode, MDagPath& pathBindMesh) { 720 | MStatus status; 721 | // Get the bind mesh connected to the message attribute of the wrap deformer 722 | MPlug plugBindMesh(oWrapNode, CVWrap::aBindDriverGeo); 723 | MPlugArray plugs; 724 | plugBindMesh.connectedTo(plugs, true, false, &status); 725 | CHECK_MSTATUS_AND_RETURN_IT(status); 726 | if (plugs.length() == 0) { 727 | MGlobal::displayError("Unable to rebind. No bind mesh is connected."); 728 | return MS::kFailure; 729 | } 730 | MObject oBindMesh = plugs[0].node(); 731 | status = MDagPath::getAPathTo(oBindMesh, pathBindMesh); 732 | CHECK_MSTATUS_AND_RETURN_IT(status); 733 | return MS::kSuccess; 734 | } 735 | 736 | 737 | 738 | MStatus CVWrapCmd::CreateRebindSubsetMesh(MDagPath& pathDriverSubset) { 739 | // We will create the mesh subset by deleting all the non-selected faces. 740 | MStatus status; 741 | 742 | MDagPath pathBindMesh; 743 | status = GetBindMesh(oWrapNode_, pathBindMesh); 744 | CHECK_MSTATUS_AND_RETURN_IT(status); 745 | MFnMesh fnBindMesh(pathBindMesh); 746 | 747 | // Duplicate the bind mesh to create subset 748 | MStringArray duplicate; 749 | // Calling mesh.duplicate() gave jacked results. 750 | status = MGlobal::executeCommand("duplicate -rr " + fnBindMesh.partialPathName(), duplicate); 751 | CHECK_MSTATUS_AND_RETURN_IT(status); 752 | status = GetDagPath(duplicate[0], pathDriverSubset); 753 | CHECK_MSTATUS_AND_RETURN_IT(status); 754 | status = DeleteIntermediateObjects(pathDriverSubset); 755 | CHECK_MSTATUS_AND_RETURN_IT(status); 756 | 757 | // Get selected driver faces 758 | MFnSingleIndexedComponent fnDriverComp(driverComponents_, &status); 759 | CHECK_MSTATUS_AND_RETURN_IT(status); 760 | MIntArray driverFaces; 761 | status = fnDriverComp.getElements(driverFaces); 762 | CHECK_MSTATUS_AND_RETURN_IT(status); 763 | 764 | int numFacesToDelete = fnBindMesh.numPolygons() - driverFaces.length(); 765 | if (numFacesToDelete) { 766 | // Get all the face ids to delete. 767 | MIntArray facesToDelete; 768 | int selectedFaceIndex = 0; 769 | for (int i = 0; i < fnBindMesh.numPolygons(); i++) { 770 | if (i != driverFaces[selectedFaceIndex]) { 771 | facesToDelete.append(i); 772 | } else { 773 | selectedFaceIndex++; 774 | } 775 | } 776 | 777 | MFnSingleIndexedComponent fnDeleteComp; 778 | MObject oFacesToDelete = fnDeleteComp.create(MFn::kMeshPolygonComponent, &status); 779 | CHECK_MSTATUS_AND_RETURN_IT(status); 780 | status = fnDeleteComp.addElements(facesToDelete); 781 | CHECK_MSTATUS_AND_RETURN_IT(status); 782 | MSelectionList deleteList; 783 | status = deleteList.add(pathDriverSubset, oFacesToDelete); 784 | CHECK_MSTATUS_AND_RETURN_IT(status); 785 | status = MGlobal::setActiveSelectionList(deleteList); 786 | CHECK_MSTATUS_AND_RETURN_IT(status); 787 | status = MGlobal::executeCommand("delete;"); 788 | CHECK_MSTATUS_AND_RETURN_IT(status); 789 | // Reacquire the the dag path since it is invalid now after deleting the faces. 790 | status = GetDagPath(duplicate[0], pathDriverSubset); 791 | CHECK_MSTATUS_AND_RETURN_IT(status); 792 | status = GetShapeNode(pathDriverSubset); 793 | CHECK_MSTATUS_AND_RETURN_IT(status); 794 | } 795 | return MS::kSuccess; 796 | } 797 | 798 | 799 | MStatus CVWrapCmd::undoIt() { 800 | MStatus status; 801 | status = dgMod_.undoIt(); 802 | CHECK_MSTATUS_AND_RETURN_IT(status); 803 | 804 | if (bindMeshes_.length()) { 805 | // Delete any created bind meshes. 806 | MDGModifier mod; 807 | for (unsigned int i = 0; i < bindMeshes_.length(); i++) { 808 | status = mod.commandToExecute("delete " + bindMeshes_[i]); 809 | CHECK_MSTATUS_AND_RETURN_IT(status); 810 | } 811 | status = mod.doIt(); 812 | CHECK_MSTATUS_AND_RETURN_IT(status); 813 | bindMeshes_.clear(); 814 | } 815 | 816 | return MS::kSuccess; 817 | } 818 | -------------------------------------------------------------------------------- /src/cvWrapDeformer.cpp: -------------------------------------------------------------------------------- 1 | #include "cvWrapDeformer.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | MTypeId CVWrap::id(0x0011580B); 17 | 18 | const char* CVWrap::kName = "cvWrap"; 19 | MObject CVWrap::aBindDriverGeo; 20 | MObject CVWrap::aDriverGeo; 21 | MObject CVWrap::aBindData; 22 | MObject CVWrap::aSampleComponents; 23 | MObject CVWrap::aSampleWeights; 24 | MObject CVWrap::aTriangleVerts; 25 | MObject CVWrap::aBarycentricWeights; 26 | MObject CVWrap::aBindMatrix; 27 | MObject CVWrap::aNumTasks; 28 | MObject CVWrap::aScale; 29 | 30 | MStatus CVWrap::initialize() { 31 | MFnCompoundAttribute cAttr; 32 | MFnMatrixAttribute mAttr; 33 | MFnMessageAttribute meAttr; 34 | MFnTypedAttribute tAttr; 35 | MFnNumericAttribute nAttr; 36 | MStatus status; 37 | 38 | aDriverGeo = tAttr.create("driver", "driver", MFnData::kMesh); 39 | status = addAttribute(aDriverGeo); 40 | CHECK_MSTATUS_AND_RETURN_IT(status); 41 | status = attributeAffects(aDriverGeo, outputGeom); 42 | CHECK_MSTATUS_AND_RETURN_IT(status); 43 | 44 | aBindDriverGeo = meAttr.create("bindMesh", "bindMesh"); 45 | status = addAttribute(aBindDriverGeo); 46 | CHECK_MSTATUS_AND_RETURN_IT(status); 47 | 48 | /* Each outputGeometry needs: 49 | -- bindData 50 | | -- sampleComponents 51 | | -- sampleWeights 52 | | -- triangleVerts 53 | | -- barycentricWeights 54 | | -- bindMatrix 55 | */ 56 | 57 | aSampleComponents = tAttr.create("sampleComponents", "sampleComponents", MFnData::kIntArray); 58 | tAttr.setArray(true); 59 | 60 | aSampleWeights = tAttr.create("sampleWeights", "sampleWeights", MFnData::kDoubleArray); 61 | tAttr.setArray(true); 62 | 63 | aTriangleVerts = nAttr.create("triangleVerts", "triangleVerts", MFnNumericData::k3Int); 64 | nAttr.setArray(true); 65 | 66 | aBarycentricWeights = nAttr.create("barycentricWeights", "barycentricWeights", MFnNumericData::k3Float); 67 | nAttr.setArray(true); 68 | 69 | aBindMatrix = mAttr.create("bindMatrix", "bindMatrix"); 70 | mAttr.setDefault(MMatrix::identity); 71 | mAttr.setArray(true); 72 | 73 | aBindData = cAttr.create("bindData", "bindData"); 74 | cAttr.setArray(true); 75 | cAttr.addChild(aSampleComponents); 76 | cAttr.addChild(aSampleWeights); 77 | cAttr.addChild(aTriangleVerts); 78 | cAttr.addChild(aBarycentricWeights); 79 | cAttr.addChild(aBindMatrix); 80 | status = addAttribute(aBindData); 81 | CHECK_MSTATUS_AND_RETURN_IT(status); 82 | status = attributeAffects(aSampleComponents, outputGeom); 83 | CHECK_MSTATUS_AND_RETURN_IT(status); 84 | status = attributeAffects(aSampleWeights, outputGeom); 85 | CHECK_MSTATUS_AND_RETURN_IT(status); 86 | status = attributeAffects(aBindMatrix, outputGeom); 87 | CHECK_MSTATUS_AND_RETURN_IT(status); 88 | status = attributeAffects(aTriangleVerts, outputGeom); 89 | CHECK_MSTATUS_AND_RETURN_IT(status); 90 | status = attributeAffects(aBarycentricWeights, outputGeom); 91 | CHECK_MSTATUS_AND_RETURN_IT(status); 92 | 93 | 94 | aScale = nAttr.create("scale", "scale", MFnNumericData::kFloat, 1.0); 95 | nAttr.setKeyable(true); 96 | status = addAttribute(aScale); 97 | CHECK_MSTATUS_AND_RETURN_IT(status); 98 | status = attributeAffects(aScale, outputGeom); 99 | CHECK_MSTATUS_AND_RETURN_IT(status); 100 | 101 | aNumTasks = nAttr.create("numTasks", "numTasks", MFnNumericData::kInt, 32); 102 | nAttr.setMin(1); 103 | nAttr.setMax(64); 104 | status = addAttribute(aNumTasks); 105 | CHECK_MSTATUS_AND_RETURN_IT(status); 106 | status = attributeAffects(aNumTasks, outputGeom); 107 | CHECK_MSTATUS_AND_RETURN_IT(status); 108 | 109 | status = MGlobal::executeCommandOnIdle("makePaintable -attrType multiFloat -sm deformer cvWrap weights"); 110 | CHECK_MSTATUS_AND_RETURN_IT(status); 111 | 112 | return MS::kSuccess; 113 | } 114 | 115 | 116 | /** 117 | Utility method used by both the MPxDeformer and MPxGPUDeformer to pull the bind data out 118 | of the datablock. 119 | @param[in] data The node datablock. 120 | @param[in] geomIndex The geometry logical index. 121 | @param[out] taskData Bind info storage. 122 | */ 123 | MStatus GetBindInfo(MDataBlock& data, unsigned int geomIndex, TaskData& taskData) { 124 | MStatus status; 125 | MArrayDataHandle hBindDataArray = data.inputArrayValue(CVWrap::aBindData); 126 | status = hBindDataArray.jumpToElement(geomIndex); 127 | CHECK_MSTATUS_AND_RETURN_IT(status); 128 | MDataHandle hBindData = hBindDataArray.inputValue(); 129 | 130 | MArrayDataHandle hSampleWeights = hBindData.child(CVWrap::aSampleWeights); 131 | unsigned int numVerts = hSampleWeights.elementCount(); 132 | if (numVerts == 0) { 133 | // No binding information yet. 134 | return MS::kNotImplemented; 135 | } 136 | MArrayDataHandle hComponents = hBindData.child(CVWrap::aSampleComponents); 137 | MArrayDataHandle hBindMatrix = hBindData.child(CVWrap::aBindMatrix); 138 | MArrayDataHandle hTriangleVerts = hBindData.child(CVWrap::aTriangleVerts); 139 | MArrayDataHandle hBarycentricWeights = hBindData.child(CVWrap::aBarycentricWeights); 140 | 141 | hSampleWeights.jumpToArrayElement(0); 142 | hComponents.jumpToArrayElement(0); 143 | hBindMatrix.jumpToArrayElement(0); 144 | hTriangleVerts.jumpToArrayElement(0); 145 | hBarycentricWeights.jumpToArrayElement(0); 146 | 147 | MFnNumericData fnNumericData; 148 | taskData.bindMatrices.setLength(numVerts); 149 | taskData.sampleIds.resize(numVerts); 150 | taskData.sampleWeights.resize(numVerts); 151 | taskData.triangleVerts.resize(numVerts); 152 | taskData.baryCoords.resize(numVerts); 153 | 154 | int sampleLength = (int)taskData.bindMatrices.length(); 155 | for (unsigned int i = 0; i < numVerts; ++i) { 156 | int logicalIndex = hComponents.elementIndex(); 157 | if (logicalIndex >= sampleLength) { 158 | // Nurbs surfaces may be sparse so make sure we have enough space. 159 | taskData.bindMatrices.setLength(logicalIndex+1); 160 | taskData.sampleIds.resize(logicalIndex+1); 161 | taskData.sampleWeights.resize(logicalIndex+1); 162 | taskData.triangleVerts.resize(logicalIndex+1); 163 | taskData.baryCoords.resize(logicalIndex+1); 164 | } 165 | 166 | // Get sample ids 167 | MObject oIndexData = hComponents.inputValue().data(); 168 | MFnIntArrayData fnIntData(oIndexData, &status); 169 | CHECK_MSTATUS_AND_RETURN_IT(status); 170 | taskData.sampleIds[logicalIndex] = fnIntData.array(); 171 | 172 | // Get sample weights 173 | MObject oWeightData = hSampleWeights.inputValue().data(); 174 | MFnDoubleArrayData fnDoubleData(oWeightData, &status); 175 | CHECK_MSTATUS_AND_RETURN_IT(status); 176 | taskData.sampleWeights[logicalIndex] = fnDoubleData.array(); 177 | assert(taskData.sampleWeights[logicalIndex].length() == taskData.sampleIds[logicalIndex].length()); 178 | 179 | // Get bind matrix 180 | taskData.bindMatrices[logicalIndex] = hBindMatrix.inputValue().asMatrix(); 181 | 182 | // Get triangle vertex binding 183 | int3& verts = hTriangleVerts.inputValue(&status).asInt3(); 184 | CHECK_MSTATUS_AND_RETURN_IT(status); 185 | MIntArray& triangleVerts = taskData.triangleVerts[logicalIndex]; 186 | triangleVerts.setLength(3); 187 | triangleVerts[0] = verts[0]; 188 | triangleVerts[1] = verts[1]; 189 | triangleVerts[2] = verts[2]; 190 | 191 | // Get barycentric weights 192 | float3& baryWeights = hBarycentricWeights.inputValue(&status).asFloat3(); 193 | CHECK_MSTATUS_AND_RETURN_IT(status); 194 | BaryCoords& coords = taskData.baryCoords[logicalIndex]; 195 | coords[0] = baryWeights[0]; 196 | coords[1] = baryWeights[1]; 197 | coords[2] = baryWeights[2]; 198 | 199 | hSampleWeights.next(); 200 | hComponents.next(); 201 | hBindMatrix.next(); 202 | hTriangleVerts.next(); 203 | hBarycentricWeights.next(); 204 | } 205 | return MS::kSuccess; 206 | } 207 | 208 | MStatus GetDriverData(MDataBlock& data, TaskData& taskData) { 209 | MStatus status; 210 | // Get driver geo 211 | MDataHandle hDriverGeo = data.inputValue(CVWrap::aDriverGeo, &status); 212 | CHECK_MSTATUS_AND_RETURN_IT(status); 213 | MObject oDriverGeo = hDriverGeo.asMesh(); 214 | CHECK_MSTATUS_AND_RETURN_IT(status); 215 | MFnMesh fnDriver(oDriverGeo, &status); 216 | CHECK_MSTATUS_AND_RETURN_IT(status); 217 | // Get the driver point positions 218 | status = fnDriver.getPoints(taskData.driverPoints, MSpace::kWorld); 219 | CHECK_MSTATUS_AND_RETURN_IT(status); 220 | unsigned int numDriverPoints = taskData.driverPoints.length(); 221 | // Get the driver normals 222 | taskData.driverNormals.setLength(numDriverPoints); 223 | fnDriver.getVertexNormals(false, taskData.driverNormals, MSpace::kWorld); 224 | return MS::kSuccess; 225 | } 226 | 227 | 228 | 229 | CVWrap::CVWrap() { 230 | MThreadPool::init(); 231 | onDeleteCallbackId = 0; 232 | } 233 | 234 | CVWrap::~CVWrap() { 235 | if (onDeleteCallbackId != 0) 236 | MMessage::removeCallback(onDeleteCallbackId); 237 | 238 | MThreadPool::release(); 239 | std::vector*>::iterator iter; 240 | for (iter = threadData_.begin(); iter != threadData_.end(); ++iter) { 241 | delete [] *iter; 242 | } 243 | threadData_.clear(); 244 | } 245 | 246 | 247 | void* CVWrap::creator() { return new CVWrap(); } 248 | 249 | void CVWrap::postConstructor() 250 | { 251 | MPxDeformerNode::postConstructor(); 252 | 253 | MStatus status = MS::kSuccess; 254 | MObject obj = thisMObject(); 255 | onDeleteCallbackId = MNodeMessage::addNodeAboutToDeleteCallback(obj, aboutToDeleteCB, NULL, &status); 256 | } 257 | 258 | void CVWrap::aboutToDeleteCB(MObject &node, MDGModifier &modifier, void *clientData) 259 | { 260 | // Find any node connected to .bindMesh and delete it with the deformer, for compatibility with wrap. 261 | MPlug bindPlug(node, aBindDriverGeo); 262 | MPlugArray bindGeometries; 263 | bindPlug.connectedTo(bindGeometries, true, false); 264 | for (unsigned int i = 0; i < bindGeometries.length(); i++) { 265 | MObject node = bindGeometries[i].node(); 266 | modifier.deleteNode(node); 267 | } 268 | } 269 | 270 | 271 | MStatus CVWrap::setDependentsDirty(const MPlug& plugBeingDirtied, MPlugArray& affectedPlugs) { 272 | // Extract the geom index from the dirty plug and set the dirty flag so we know that we need to 273 | // re-read the binding data. 274 | if (plugBeingDirtied.isElement()) { 275 | MPlug parent = plugBeingDirtied.array().parent(); 276 | if (parent == aBindData) { 277 | unsigned int geomIndex = parent.logicalIndex(); 278 | dirty_[geomIndex] = true; 279 | } 280 | } 281 | return MS::kSuccess; 282 | } 283 | 284 | 285 | MStatus CVWrap::deform(MDataBlock& data, MItGeometry& itGeo, const MMatrix& localToWorldMatrix, 286 | unsigned int geomIndex) { 287 | MStatus status; 288 | if (geomIndex >= taskData_.size()) { 289 | taskData_.resize(geomIndex+1); 290 | } 291 | TaskData& taskData = taskData_[geomIndex]; 292 | 293 | // Get driver geo 294 | MDataHandle hDriverGeo = data.inputValue(aDriverGeo, &status); 295 | CHECK_MSTATUS_AND_RETURN_IT(status); 296 | MObject oDriverGeo = hDriverGeo.asMesh(); 297 | CHECK_MSTATUS_AND_RETURN_IT(status); 298 | if (oDriverGeo.isNull()) { 299 | // Without a driver mesh, we can't do anything 300 | return MS::kSuccess; 301 | } 302 | 303 | // Only pull bind information from the data block if it is dirty 304 | if (dirty_[geomIndex] || taskData.sampleIds.size() == 0) { 305 | dirty_[geomIndex] = false; 306 | status = GetBindInfo(data, geomIndex, taskData); 307 | if (status == MS::kNotImplemented) { 308 | // If no bind information is stored yet, don't do anything. 309 | return MS::kSuccess; 310 | } else if (MFAIL(status)) { 311 | CHECK_MSTATUS_AND_RETURN_IT(status); 312 | } 313 | } 314 | 315 | // Get driver geo information 316 | MFnMesh fnDriver(oDriverGeo, &status); 317 | CHECK_MSTATUS_AND_RETURN_IT(status); 318 | // Get the driver point positions 319 | status = fnDriver.getPoints(taskData.driverPoints, MSpace::kWorld); 320 | CHECK_MSTATUS_AND_RETURN_IT(status); 321 | unsigned int numDriverPoints = taskData.driverPoints.length(); 322 | // Get the driver normals 323 | taskData.driverNormals.setLength(numDriverPoints); 324 | fnDriver.getVertexNormals(false, taskData.driverNormals, MSpace::kWorld); 325 | 326 | // Get the deformer membership and paint weights 327 | unsigned int membershipCount = itGeo.count(); 328 | taskData.membership.setLength(membershipCount); 329 | taskData.paintWeights.setLength(membershipCount); 330 | status = itGeo.allPositions(taskData.points); 331 | CHECK_MSTATUS_AND_RETURN_IT(status); 332 | for (int i = 0; !itGeo.isDone(); itGeo.next(), i++) { 333 | taskData.membership[i] = itGeo.index(); 334 | taskData.paintWeights[i] = weightValue(data, geomIndex, itGeo.index()); 335 | } 336 | 337 | taskData.drivenMatrix = localToWorldMatrix; 338 | taskData.drivenInverseMatrix = localToWorldMatrix.inverse(); 339 | 340 | // See if we even need to calculate anything. 341 | taskData.scale = data.inputValue(aScale).asFloat(); 342 | taskData.envelope = data.inputValue(envelope).asFloat(); 343 | int taskCount = data.inputValue(aNumTasks).asInt(); 344 | if (taskData.envelope == 0.0f || taskCount <= 0) { 345 | return MS::kSuccess; 346 | } 347 | 348 | if (geomIndex >= threadData_.size()) { 349 | // Make sure a ThreadData objects exist for this geomIndex. 350 | size_t currentSize = threadData_.size(); 351 | threadData_.resize(geomIndex+1); 352 | for (size_t i = currentSize; i < geomIndex+1; ++i) { 353 | threadData_[i] = new ThreadData[taskCount]; 354 | } 355 | } else { 356 | // Make sure the number of ThreadData instances is correct for this geomIndex 357 | if (threadData_[geomIndex][0].numTasks != taskCount) { 358 | delete [] threadData_[geomIndex]; 359 | threadData_[geomIndex] = new ThreadData[taskCount]; 360 | } 361 | } 362 | 363 | CreateThreadData(taskCount, taskData_[geomIndex].points.length(), 364 | &taskData_[geomIndex], threadData_[geomIndex]); 365 | MThreadPool::newParallelRegion(CreateTasks, (void *)threadData_[geomIndex]); 366 | 367 | status = itGeo.setAllPositions(taskData.points); 368 | CHECK_MSTATUS_AND_RETURN_IT(status); 369 | 370 | return MS::kSuccess; 371 | } 372 | 373 | 374 | void CVWrap::CreateTasks(void *data, MThreadRootTask *pRoot) { 375 | ThreadData* threadData = static_cast*>(data); 376 | 377 | if (threadData) { 378 | int numTasks = threadData[0].numTasks; 379 | for(int i = 0; i < numTasks; i++) { 380 | MThreadPool::createTask(EvaluateWrap, (void *)&threadData[i], pRoot); 381 | } 382 | MThreadPool::executeAndJoin(pRoot); 383 | } 384 | } 385 | 386 | 387 | MThreadRetVal CVWrap::EvaluateWrap(void *pParam) { 388 | ThreadData* pThreadData = static_cast*>(pParam); 389 | double*& alignedStorage = pThreadData->alignedStorage; 390 | TaskData* pData = pThreadData->pData; 391 | // Get the data out of the struct so it is easier to work with. 392 | MMatrix& drivenMatrix = pData->drivenMatrix; 393 | MMatrix& drivenInverseMatrix = pData->drivenInverseMatrix; 394 | float env = pThreadData->pData->envelope; 395 | float scale = pThreadData->pData->scale; 396 | MIntArray& membership = pData->membership; 397 | MFloatArray& paintWeights = pData->paintWeights; 398 | MPointArray& points = pData->points; 399 | MPointArray& driverPoints = pData->driverPoints; 400 | MFloatVectorArray& driverNormals = pData->driverNormals; 401 | MMatrixArray& bindMatrices = pData->bindMatrices; 402 | std::vector & sampleIds = pData->sampleIds; 403 | std::vector & sampleWeights = pData->sampleWeights; 404 | std::vector & triangleVerts = pData->triangleVerts; 405 | std::vector & baryCoords = pData->baryCoords; 406 | 407 | unsigned int taskStart = pThreadData->start; 408 | unsigned int taskEnd = pThreadData->end; 409 | 410 | MPoint newPt; 411 | MMatrix scaleMatrix, matrix; 412 | scaleMatrix[0][0] = scale; 413 | scaleMatrix[1][1] = scale; 414 | scaleMatrix[2][2] = scale; 415 | for (unsigned int i = taskStart; i < taskEnd; ++i) { 416 | if (i >= points.length()) { 417 | break; 418 | } 419 | int index = membership[i]; 420 | 421 | MPoint origin; 422 | MVector normal, up; 423 | CalculateBasisComponents(sampleWeights[index], baryCoords[index], triangleVerts[index], 424 | driverPoints, driverNormals, sampleIds[index], alignedStorage, 425 | origin, up, normal); 426 | 427 | CreateMatrix(origin, normal, up, matrix); 428 | matrix = scaleMatrix * matrix; 429 | MPoint newPt = ((points[i] * drivenMatrix) * (bindMatrices[index] * matrix)) * drivenInverseMatrix; 430 | points[i] = points[i] + ((newPt - points[i]) * paintWeights[i] * env); 431 | } 432 | return 0; 433 | } 434 | 435 | 436 | #if MAYA_API_VERSION >= 201600 437 | MString CVWrapGPU::pluginLoadPath; 438 | 439 | #if MAYA_API_VERSION >= 201650 440 | cl_command_queue (*getMayaDefaultOpenCLCommandQueue)() = MOpenCLInfo::getMayaDefaultOpenCLCommandQueue; 441 | #else 442 | cl_command_queue (*getMayaDefaultOpenCLCommandQueue)() = MOpenCLInfo::getOpenCLCommandQueue; 443 | #endif 444 | /** 445 | Convenience function to copy array data to the gpu. 446 | */ 447 | cl_int EnqueueBuffer(MAutoCLMem& mclMem, size_t bufferSize, void* data) { 448 | cl_int err = CL_SUCCESS; 449 | if (!mclMem.get()) { 450 | // The buffer doesn't exist yet so create it and copy the data over. 451 | mclMem.attach(clCreateBuffer(MOpenCLInfo::getOpenCLContext(), 452 | CL_MEM_COPY_HOST_PTR | CL_MEM_READ_ONLY, 453 | bufferSize, data, &err)); 454 | } else { 455 | // The buffer already exists so just copy the data over. 456 | err = clEnqueueWriteBuffer(getMayaDefaultOpenCLCommandQueue(), 457 | mclMem.get(), CL_TRUE, 0, bufferSize, 458 | data, 0, NULL, NULL); 459 | } 460 | return err; 461 | } 462 | 463 | MGPUDeformerRegistrationInfo* CVWrapGPU::GetGPUDeformerInfo() { 464 | static CVWrapGPUDeformerInfo wrapInfo; 465 | return &wrapInfo; 466 | } 467 | 468 | CVWrapGPU::CVWrapGPU() { 469 | // Remember the ctor must be fast. No heavy work should be done here. 470 | // Maya may allocate one of these and then never use it. 471 | } 472 | 473 | CVWrapGPU::~CVWrapGPU() { 474 | terminate(); 475 | } 476 | 477 | #if MAYA_API_VERSION <= 201700 478 | MPxGPUDeformer::DeformerStatus CVWrapGPU::evaluate(MDataBlock& block, 479 | const MEvaluationNode& evaluationNode, 480 | const MPlug& plug, 481 | unsigned int numElements, 482 | const MAutoCLMem inputBuffer, 483 | const MAutoCLEvent inputEvent, 484 | MAutoCLMem outputBuffer, 485 | MAutoCLEvent& outputEvent) { 486 | #else 487 | MPxGPUDeformer::DeformerStatus CVWrapGPU::evaluate(MDataBlock& block, 488 | const MEvaluationNode& evaluationNode, 489 | const MPlug& plug, 490 | const MGPUDeformerData& inputData, 491 | MGPUDeformerData& outputData) { 492 | // get the input GPU data and event 493 | MGPUDeformerBuffer inputDeformerBuffer = inputData.getBuffer(sPositionsName()); 494 | const MAutoCLMem inputBuffer = inputDeformerBuffer.buffer(); 495 | unsigned int numElements = inputDeformerBuffer.elementCount(); 496 | const MAutoCLEvent inputEvent = inputDeformerBuffer.bufferReadyEvent(); 497 | 498 | // create the output buffer 499 | MGPUDeformerBuffer outputDeformerBuffer = createOutputBuffer(inputDeformerBuffer); 500 | MAutoCLEvent outputEvent; 501 | MAutoCLMem outputBuffer = outputDeformerBuffer.buffer(); 502 | #endif 503 | 504 | MStatus status; 505 | numElements_ = numElements; 506 | // Copy all necessary data to the gpu. 507 | status = EnqueueBindData(block, evaluationNode, plug); 508 | CHECK_MSTATUS(status); 509 | status = EnqueueDriverData(block, evaluationNode, plug); 510 | CHECK_MSTATUS(status); 511 | status = EnqueuePaintMapData(block, evaluationNode, numElements, plug); 512 | CHECK_MSTATUS(status); 513 | 514 | if (!kernel_.get()) { 515 | // Load the OpenCL kernel if we haven't yet. 516 | MString openCLKernelFile(pluginLoadPath); 517 | #if MAYA_API_VERSION > 201700 518 | openCLKernelFile += "/cvwrap.cl"; 519 | #else 520 | openCLKernelFile += "/cvwrap_pre2018.cl"; 521 | #endif 522 | kernel_ = MOpenCLInfo::getOpenCLKernel(openCLKernelFile, "cvwrap"); 523 | if (kernel_.isNull()) { 524 | std::cerr << "Could not compile kernel " << openCLKernelFile.asChar() << "\n"; 525 | return MPxGPUDeformer::kDeformerFailure; 526 | } 527 | } 528 | float envelope = block.inputValue(MPxDeformerNode::envelope, &status).asFloat(); 529 | CHECK_MSTATUS(status); 530 | cl_int err = CL_SUCCESS; 531 | 532 | // Set all of our kernel parameters. Input buffer and output buffer may be changing every frame 533 | // so always set them. 534 | unsigned int parameterId = 0; 535 | err = clSetKernelArg(kernel_.get(), parameterId++, sizeof(cl_mem), (void*)outputBuffer.getReadOnlyRef()); 536 | MOpenCLInfo::checkCLErrorStatus(err); 537 | err = clSetKernelArg(kernel_.get(), parameterId++, sizeof(cl_mem), (void*)inputBuffer.getReadOnlyRef()); 538 | MOpenCLInfo::checkCLErrorStatus(err); 539 | err = clSetKernelArg(kernel_.get(), parameterId++, sizeof(cl_mem), (void*)driverPoints_.getReadOnlyRef()); 540 | MOpenCLInfo::checkCLErrorStatus(err); 541 | err = clSetKernelArg(kernel_.get(), parameterId++, sizeof(cl_mem), (void*)driverNormals_.getReadOnlyRef()); 542 | MOpenCLInfo::checkCLErrorStatus(err); 543 | err = clSetKernelArg(kernel_.get(), parameterId++, sizeof(cl_mem), (void*)paintWeights_.getReadOnlyRef()); 544 | MOpenCLInfo::checkCLErrorStatus(err); 545 | err = clSetKernelArg(kernel_.get(), parameterId++, sizeof(cl_mem), (void*)sampleCounts_.getReadOnlyRef()); 546 | MOpenCLInfo::checkCLErrorStatus(err); 547 | err = clSetKernelArg(kernel_.get(), parameterId++, sizeof(cl_mem), (void*)sampleOffsets_.getReadOnlyRef()); 548 | MOpenCLInfo::checkCLErrorStatus(err); 549 | err = clSetKernelArg(kernel_.get(), parameterId++, sizeof(cl_mem), (void*)sampleIds_.getReadOnlyRef()); 550 | MOpenCLInfo::checkCLErrorStatus(err); 551 | err = clSetKernelArg(kernel_.get(), parameterId++, sizeof(cl_mem), (void*)sampleWeights_.getReadOnlyRef()); 552 | MOpenCLInfo::checkCLErrorStatus(err); 553 | err = clSetKernelArg(kernel_.get(), parameterId++, sizeof(cl_mem), (void*)triangleVerts_.getReadOnlyRef()); 554 | MOpenCLInfo::checkCLErrorStatus(err); 555 | err = clSetKernelArg(kernel_.get(), parameterId++, sizeof(cl_mem), (void*)baryCoords_.getReadOnlyRef()); 556 | MOpenCLInfo::checkCLErrorStatus(err); 557 | err = clSetKernelArg(kernel_.get(), parameterId++, sizeof(cl_mem), (void*)bindMatrices_.getReadOnlyRef()); 558 | MOpenCLInfo::checkCLErrorStatus(err); 559 | err = clSetKernelArg(kernel_.get(), parameterId++, sizeof(cl_mem), (void*)drivenMatrices_.getReadOnlyRef()); 560 | MOpenCLInfo::checkCLErrorStatus(err); 561 | #if MAYA_API_VERSION > 201700 562 | // get the world space and inverse world space matrix mem handles 563 | MGPUDeformerBuffer inputWorldSpaceMatrixDeformerBuffer = inputData.getBuffer(sGeometryMatrixName()); 564 | const MAutoCLMem deformerWorldSpaceMatrix = inputWorldSpaceMatrixDeformerBuffer.buffer(); 565 | MGPUDeformerBuffer inputInvWorldSpaceMatrixDeformerBuffer = inputData.getBuffer(sInverseGeometryMatrixName()); 566 | const MAutoCLMem deformerInvWorldSpaceMatrix = inputInvWorldSpaceMatrixDeformerBuffer.buffer(); 567 | // Note: these matrices are in row major order 568 | err = clSetKernelArg(kernel_.get(), parameterId++, sizeof(cl_mem), (void*)deformerWorldSpaceMatrix.getReadOnlyRef()); 569 | MOpenCLInfo::checkCLErrorStatus(err); 570 | err = clSetKernelArg(kernel_.get(), parameterId++, sizeof(cl_mem), (void*)deformerInvWorldSpaceMatrix.getReadOnlyRef()); 571 | MOpenCLInfo::checkCLErrorStatus(err); 572 | #endif 573 | err = clSetKernelArg(kernel_.get(), parameterId++, sizeof(cl_float), (void*)&envelope); 574 | MOpenCLInfo::checkCLErrorStatus(err); 575 | err = clSetKernelArg(kernel_.get(), parameterId++, sizeof(cl_uint), (void*)&numElements_); 576 | MOpenCLInfo::checkCLErrorStatus(err); 577 | 578 | // Figure out a good work group size for our kernel. 579 | size_t workGroupSize; 580 | size_t retSize; 581 | err = clGetKernelWorkGroupInfo( 582 | kernel_.get(), 583 | MOpenCLInfo::getOpenCLDeviceId(), 584 | CL_KERNEL_WORK_GROUP_SIZE, 585 | sizeof(size_t), 586 | &workGroupSize, 587 | &retSize); 588 | MOpenCLInfo::checkCLErrorStatus(err); 589 | 590 | size_t localWorkSize = 256; 591 | if (retSize > 0) { 592 | localWorkSize = workGroupSize; 593 | } 594 | // global work size must be a multiple of localWorkSize 595 | size_t globalWorkSize = (localWorkSize - numElements_ % localWorkSize) + numElements_; 596 | 597 | // set up our input events. The input event could be NULL, in that case we need to pass 598 | // slightly different parameters into clEnqueueNDRangeKernel 599 | unsigned int numInputEvents = 0; 600 | if (inputEvent.get()) { 601 | numInputEvents = 1; 602 | } 603 | 604 | // run the kernel 605 | err = clEnqueueNDRangeKernel( 606 | getMayaDefaultOpenCLCommandQueue(), 607 | kernel_.get(), 608 | 1, 609 | NULL, 610 | &globalWorkSize, 611 | &localWorkSize, 612 | numInputEvents, 613 | numInputEvents ? inputEvent.getReadOnlyRef() : 0, 614 | outputEvent.getReferenceForAssignment() ); 615 | MOpenCLInfo::checkCLErrorStatus(err); 616 | 617 | #if MAYA_API_VERSION > 201700 618 | // set the buffer into the output data 619 | outputDeformerBuffer.setBufferReadyEvent(outputEvent); 620 | outputData.setBuffer(outputDeformerBuffer); 621 | #endif 622 | 623 | return MPxGPUDeformer::kDeformerSuccess; 624 | } 625 | 626 | MStatus CVWrapGPU::EnqueueBindData(MDataBlock& data, const MEvaluationNode& evaluationNode, 627 | const MPlug& plug) { 628 | MStatus status; 629 | if ((bindMatrices_.get() && ( 630 | !evaluationNode.dirtyPlugExists(CVWrap::aBindData, &status) && 631 | !evaluationNode.dirtyPlugExists(CVWrap::aSampleComponents, &status) && 632 | !evaluationNode.dirtyPlugExists(CVWrap::aSampleWeights, &status) && 633 | !evaluationNode.dirtyPlugExists(CVWrap::aTriangleVerts, &status) && 634 | !evaluationNode.dirtyPlugExists(CVWrap::aBarycentricWeights, &status) && 635 | !evaluationNode.dirtyPlugExists(CVWrap::aBindMatrix, &status) 636 | )) || !status) { 637 | // No bind data has changed, nothing to do. 638 | return MS::kSuccess; 639 | } 640 | 641 | TaskData taskData; 642 | unsigned int geomIndex = plug.logicalIndex(); 643 | status = GetBindInfo(data, geomIndex, taskData); 644 | CHECK_MSTATUS_AND_RETURN_IT(status); 645 | 646 | // Flatten out bind matrices to float array 647 | size_t arraySize = taskData.bindMatrices.length() * 16; 648 | float* bindMatrices = new float[arraySize]; 649 | for(unsigned int i = 0, idx = 0; i < taskData.bindMatrices.length(); ++i) { 650 | for(unsigned int row = 0; row < 4; row++) { 651 | for(unsigned int column = 0; column < 4; column++) { 652 | bindMatrices[idx++] = (float)taskData.bindMatrices[i](row, column); 653 | } 654 | } 655 | } 656 | cl_int err = EnqueueBuffer(bindMatrices_, arraySize * sizeof(float), (void*)bindMatrices); 657 | delete [] bindMatrices; 658 | 659 | // Store samples per vertex 660 | arraySize = taskData.sampleIds.size(); 661 | int* samplesPerVertex = new int[arraySize]; 662 | int* sampleOffsets = new int[arraySize]; 663 | int totalSamples = 0; 664 | for(size_t i = 0; i < taskData.sampleIds.size(); ++i) { 665 | samplesPerVertex[i] = (int)taskData.sampleIds[i].length(); 666 | sampleOffsets[i] = totalSamples; 667 | totalSamples += samplesPerVertex[i]; 668 | } 669 | err = EnqueueBuffer(sampleCounts_, arraySize * sizeof(int), (void*)samplesPerVertex); 670 | err = EnqueueBuffer(sampleOffsets_, arraySize * sizeof(int), (void*)sampleOffsets); 671 | delete [] samplesPerVertex; 672 | delete [] sampleOffsets; 673 | 674 | // Store sampleIds and sampleWeights 675 | int* sampleIds = new int[totalSamples]; 676 | float* sampleWeights = new float[totalSamples]; 677 | int iter = 0; 678 | for(size_t i = 0; i < taskData.sampleIds.size(); ++i) { 679 | for(unsigned int j = 0; j < taskData.sampleIds[i].length(); ++j) { 680 | sampleIds[iter] = taskData.sampleIds[i][j]; 681 | sampleWeights[iter] = (float)taskData.sampleWeights[i][j]; 682 | iter++; 683 | } 684 | } 685 | err = EnqueueBuffer(sampleIds_, totalSamples * sizeof(int), (void*)sampleIds); 686 | err = EnqueueBuffer(sampleWeights_, totalSamples * sizeof(float), (void*)sampleWeights); 687 | delete [] sampleIds; 688 | delete [] sampleWeights; 689 | 690 | // Store triangle verts and bary coords 691 | arraySize = taskData.triangleVerts.size() * 3; 692 | int* triangleVerts = new int[arraySize]; 693 | float* baryCoords = new float[arraySize]; 694 | iter = 0; 695 | for(size_t i = 0; i < taskData.triangleVerts.size(); ++i) { 696 | for(unsigned int j = 0; j < 3; ++j) { 697 | triangleVerts[iter] = taskData.triangleVerts[i][j]; 698 | baryCoords[iter] = (float)taskData.baryCoords[i][j]; 699 | iter++; 700 | } 701 | } 702 | err = EnqueueBuffer(triangleVerts_, arraySize * sizeof(int), (void*)triangleVerts); 703 | err = EnqueueBuffer(baryCoords_, arraySize * sizeof(float), (void*)baryCoords); 704 | delete [] triangleVerts; 705 | delete [] baryCoords; 706 | return MS::kSuccess; 707 | } 708 | 709 | 710 | MStatus CVWrapGPU::EnqueueDriverData(MDataBlock& data, const MEvaluationNode& evaluationNode, const MPlug& plug) { 711 | MStatus status; 712 | TaskData taskData; 713 | status = GetDriverData(data, taskData); 714 | CHECK_MSTATUS_AND_RETURN_IT(status); 715 | cl_int err = CL_SUCCESS; 716 | // Store world space driver points and normals into float arrays. 717 | // Reuse the same array for points and normals so we're not dynamically allocating double 718 | // the memory. 719 | unsigned int pointCount = taskData.driverPoints.length(); 720 | float* driverData = new float[pointCount * 3]; 721 | 722 | // Store the driver points on the gpu. 723 | for (unsigned int i = 0, iter = 0; i < pointCount; ++i) { 724 | driverData[iter++] = (float)taskData.driverPoints[i].x; 725 | driverData[iter++] = (float)taskData.driverPoints[i].y; 726 | driverData[iter++] = (float)taskData.driverPoints[i].z; 727 | } 728 | err = EnqueueBuffer(driverPoints_, pointCount * 3 * sizeof(float), (void*)driverData); 729 | 730 | // Store the driver normals on the gpu. 731 | for (unsigned int i = 0, iter = 0; i < pointCount; ++i) { 732 | driverData[iter++] = taskData.driverNormals[i].x; 733 | driverData[iter++] = taskData.driverNormals[i].y; 734 | driverData[iter++] = taskData.driverNormals[i].z; 735 | } 736 | err = EnqueueBuffer(driverNormals_, pointCount * 3 * sizeof(float), (void*)driverData); 737 | delete [] driverData; 738 | 739 | int idx = 0; 740 | #if MAYA_API_VERSION <= 201700 741 | // Store the driven matrices on the gpu. 742 | MArrayDataHandle hInputs = data.inputValue(CVWrap::input, &status); 743 | unsigned int geomIndex = plug.logicalIndex(); 744 | status = hInputs.jumpToElement(geomIndex); 745 | CHECK_MSTATUS_AND_RETURN_IT(status); 746 | MDataHandle hInput = hInputs.inputValue(&status); 747 | CHECK_MSTATUS_AND_RETURN_IT(status); 748 | MDataHandle hGeom = hInput.child(CVWrap::inputGeom); 749 | MMatrix localToWorldMatrix = hGeom.geometryTransformMatrix(); 750 | MMatrix worldToLocalMatrix = localToWorldMatrix.inverse(); 751 | float drivenMatrices[48]; // 0-15: localToWorld, 16-31: worldToLocal, 32-47: scale 752 | 753 | // Store in column order so we can dot in the cl kernel. 754 | for(unsigned int column = 0; column < 4; column++) { 755 | for(unsigned int row = 0; row < 4; row++) { 756 | drivenMatrices[idx++] = (float)localToWorldMatrix(row, column); 757 | } 758 | } 759 | for(unsigned int column = 0; column < 4; column++) { 760 | for(unsigned int row = 0; row < 4; row++) { 761 | drivenMatrices[idx++] = (float)worldToLocalMatrix(row, column); 762 | } 763 | } 764 | #else 765 | float drivenMatrices[16]; // 0-15: scale 766 | #endif 767 | // Scale matrix is stored row major 768 | float scale = data.inputValue(CVWrap::aScale, &status).asFloat(); 769 | CHECK_MSTATUS_AND_RETURN_IT(status); 770 | MMatrix scaleMatrix; 771 | scaleMatrix[0][0] = scale; 772 | scaleMatrix[1][1] = scale; 773 | scaleMatrix[2][2] = scale; 774 | for(unsigned int row = 0; row < 4; row++) { 775 | for(unsigned int column = 0; column < 4; column++) { 776 | drivenMatrices[idx++] = (float)scaleMatrix(row, column); 777 | } 778 | } 779 | #if MAYA_API_VERSION <= 201700 780 | err = EnqueueBuffer(drivenMatrices_, 48 * sizeof(float), (void*)drivenMatrices); 781 | #else 782 | err = EnqueueBuffer(drivenMatrices_, 16 * sizeof(float), (void*)drivenMatrices); 783 | #endif 784 | return MS::kSuccess; 785 | } 786 | 787 | 788 | MStatus CVWrapGPU::EnqueuePaintMapData(MDataBlock& data, 789 | const MEvaluationNode& evaluationNode, 790 | unsigned int numElements, 791 | const MPlug& plug) { 792 | MStatus status; 793 | if ((paintWeights_.get() && 794 | !evaluationNode.dirtyPlugExists(MPxDeformerNode::weightList, &status)) || !status) { 795 | // The paint weights are not dirty so no need to get them. 796 | return MS::kSuccess; 797 | } 798 | 799 | cl_int err = CL_SUCCESS; 800 | 801 | // Store the paint weights on the gpu. 802 | // Since we can't call MPxDeformerNode::weightValue, get the paint weights from the data block. 803 | float* paintWeights = new float[numElements]; 804 | MArrayDataHandle weightList = data.outputArrayValue(MPxDeformerNode::weightList, &status); 805 | CHECK_MSTATUS_AND_RETURN_IT(status); 806 | unsigned int geomIndex = plug.logicalIndex(); 807 | status = weightList.jumpToElement(geomIndex); 808 | // it is possible that the jumpToElement fails. In that case all weights are 1. 809 | if (!status) { 810 | for(unsigned int i = 0; i < numElements; i++) { 811 | paintWeights[i] = 1.0f; 812 | } 813 | } else { 814 | // Initialize all weights to 1.0f 815 | for(unsigned int i = 0; i < numElements; i++) { 816 | paintWeights[i] = 1.0f; 817 | } 818 | MDataHandle weightsStructure = weightList.inputValue(&status); 819 | CHECK_MSTATUS_AND_RETURN_IT(status); 820 | MArrayDataHandle weights = weightsStructure.child(MPxDeformerNode::weights); 821 | CHECK_MSTATUS_AND_RETURN_IT(status); 822 | // Gather all the non-zero weights 823 | unsigned int numWeights = weights.elementCount(&status); 824 | CHECK_MSTATUS_AND_RETURN_IT(status); 825 | for (unsigned int i = 0; i < numWeights; i++, weights.next()) { 826 | unsigned int weightsElementIndex = weights.elementIndex(&status); 827 | MDataHandle value = weights.inputValue(&status); 828 | // BUG: The weightsElementIndex may be sparse for nurbs surfaces so this would be incorrect 829 | paintWeights[weightsElementIndex] = value.asFloat(); 830 | } 831 | } 832 | err = EnqueueBuffer(paintWeights_, numElements * sizeof(float), (void*)paintWeights); 833 | delete [] paintWeights; 834 | return MS::kSuccess; 835 | } 836 | 837 | 838 | void CVWrapGPU::terminate() { 839 | driverPoints_.reset(); 840 | driverNormals_.reset(); 841 | paintWeights_.reset(); 842 | bindMatrices_.reset(); 843 | sampleCounts_.reset(); 844 | sampleIds_.reset(); 845 | sampleWeights_.reset(); 846 | triangleVerts_.reset(); 847 | baryCoords_.reset(); 848 | drivenMatrices_.reset(); 849 | MOpenCLInfo::releaseOpenCLKernel(kernel_); 850 | kernel_.reset(); 851 | } 852 | 853 | #endif 854 | 855 | --------------------------------------------------------------------------------