Dynamic section tool in Autodesk Alias is an awfully practical tool for automotive modellers, unfortunately it didn’t exist in Maya… Until now!
Section tool is actually my very first C++ plugin. It displays cross sections of input geometry along given axis. Section plane orientation and distance can be easily set by changing node’s transformations, through custom manipulator or attribute editor. Just like the one in Autodesk Alias. It works in legacy viewport as well as Viewport 2.0.
Update 2016-10-10: Turned out I haven’t tested the Viewport 2.0 thoroughly and it’s not working correctly. Also the manip does funny things when part of another transform.
Update 2017-01-30: Check the new version of this plugin.
How does the section tool work?
Since Maya API doesn’t provide any direct method for intersecting meshes I came up with the following workflow.
- Smooth all input meshes according to their smooth display options.
- Join meshes in a single work mesh
- Remove faces whose bounding box doesn’t intersect with the section plane(s)
- Calculate intersection of each face edge with section plane:
The work mesh
First step is pretty straightforward. We load all the data and generate smooth meshes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// Mesh data creator MFnMeshData dataCreator; MObject meshData = dataCreator.create(&status); // Smooth mesh option MMeshSmoothOptions smoothOptions; // Get a data handle from the compound attribute, which contains all input meshes and their display options MArrayDataHandle arrayHandle = data.inputArrayValue(aMeshes, &status); for (unsigned i = 0; i < arrayHandle.elementCount(); i++) { // Get compound handle status = arrayHandle.jumpToElement(i); MDataHandle compoundHandle = arrayHandle.inputValue(&status); // Get child handles MDataHandle hInMesh = compoundHandle.child(aInMesh); MDataHandle hDisplaySmoothMesh = compoundHandle.child(aDisplaySmoothMesh); MDataHandle hSmoothLevel = compoundHandle.child(aSmoothLevel); // Load input mesh MObject oInMesh = hInMesh.asMeshTransformed(); MFnMesh fnMesh(oInMesh, &status); // Set smooth level based on mesh display options smoothOptions.setDivisions(hSmoothLevel.asInt(), &status); // If not smoothed, copy input mesh, otherwise generate a smooth mesh MObject workMesh = (hDisplaySmoothMesh.asInt() > 0) ? fnMesh.generateSmoothMesh(meshData, &smoothOptions, &status) : fnMesh.copy(oInMesh, meshData, &status); // Later we are going to load data structure of the new mesh here } |
That was easy. Joining meshes however isn’t nearly as simple as you’d expect. There’s surprisingly no merge or join method in the function set. Luckily we can get data structure of each mesh, merge them all together and use the new data structure to generate a new mesh as described in this thread. To create the merged work mesh we first have to define the following variables.
1 2 3 4 5 |
int numVertices = 0; int numPolygons = 0; MPointArray vertexArray; MIntArray polygonCounts; MIntArray polygonConnects; |
Now we add info about each mesh. Pay attention especially to the last bit.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// Get a functionset for our smooth mesh MFnMesh fnSmoothMesh(meshData); // Get length of the list unsigned start = numVertices; // For these we perform a simple in place addition numVertices += fnSmoothMesh.numVertices(&status); numPolygons += fnSmoothMesh.numPolygons(&status); // Get info about vertices MPointArray meshPoints; MIntArray meshCounts; MIntArray meshConnects; status = fnSmoothMesh.getPoints(meshPoints, MSpace::kWorld); status = fnSmoothMesh.getVertices(meshCounts, meshConnects); // We simply append all points and per-face counts for (unsigned j = 0; j < meshPoints.length(); j++) vertexArray.append(meshPoints[j]); for (unsigned j = 0; j < meshCounts.length(); j++) polygonCounts.append(meshCounts[j]); // This one is a bit tricky. By appending points to the new list we unintentionally changed their indices. Therefore we have to correct them. for (unsigned j = 0; j < meshConnects.length(); j++) polygonConnects.append(meshConnects[j]+start); |
Finally the work mesh can be created.
1 2 3 4 5 6 7 8 9 10 11 |
MObject newMeshData = dataCreator.create(&status); MFnMesh fnWorkMesh; MObject oWorkMesh = fnWorkMesh.create( numVertices, numPolygons, vertexArray, polygonCounts, polygonConnects, newMeshData, &status); |
The intersection
We have the work mesh, great! We still need to create a set of planes to intersect it. We’re going to set the base plane by defining its normal vector. By applying node’s world mesh to the plane normal, we can change orientation by simply transforming the locator node.
1 2 3 4 5 6 7 8 9 10 |
// Load attributes MDataHandle hInputMatrix = data.inputValue(aMatrix, &status); inputMatrix = hInputMatrix.asMatrix(); MDataHandle hNumberOfPlanes = data.inputValue(aNumberOfPlanes, &status); unsigned int numberOfPlanes = hNumberOfPlanes.asInt(); // Define section plane and apply node's transformation MVector vectorX(1.0f, 0.0f, 0.0f); vectorX *= inputMatrix; |
Now that we have all that we need for calculation, we can loop through mesh faces, calculate the intersection and create curve segments. But first, let’s check if bounding boxes of the polygon/plane intersect at all. This won’t give us the curve we want, but it will give us more speed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
MDistance conversion; // Storage for newly created section segments MObjectArray sectionCurves; // Data creator just like the one we need to create a mesh. MFnNurbsCurveData crvDataCreator; for (int i=0; i < fnWorkMesh.numPolygons(&status); i++) { MIntArray faceVerticesId; fnWorkMesh.getPolygonVertices(i, faceVerticesId); MPointArray faceVertices; // Create a bounding box containing the polygon MBoundingBox bBox; for (unsigned int i = 0; i < faceVerticesId.length(); i++) { MPoint vertex; fnWorkMesh.getPoint(faceVerticesId[i], vertex, MSpace::kWorld); faceVertices.append(vertex); bBox.expand(vertex); } for (unsigned int i = 0; i < numberOfPlanes; i++) { // Since the normal defines only plane's orientation, not position, we have to set plane's origin double spacing = conversion.uiToInternal(i); MPoint planePoint(spacing, 0, 0); planePoint *= inputMatrix; // Plane's bounding box MBoundingBox pBox(MPoint(spacing, -999999, -999999), MPoint(spacing, 999999, 999999)); pBox.transformUsing(inputMatrix); // If bounding boxes intersect, perform calculation if (bBox.intersects(pBox)){ MPointArray intersection = getFaceIntersection(faceVertices, vectorX, planePoint); if (intersection.length() > 1) { MObject crvData = crvDataCreator.create(&status); MFnNurbsCurve fnCurve; MObject sectionCrv = fnCurve.createWithEditPoints( intersection, 1, MFnNurbsCurve::kOpen, false, false, true, crvData, &status); sectionCurves.append(crvData); } } } } |
The actual intersection algorithm is based on a simple 3D Line Segment and Plane Intersection method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
MPointArray SectionTool::getFaceIntersection(const MPointArray& faceVertices, const MVector& planeNormal, const MPoint& planePoint) { MPointArray intersection; for (unsigned int i = 0; i < faceVertices.length(); i++) { // Get second vector point int second = (i == (faceVertices.length() - 1)) ? 0 : (i + 1); // Prepare ray for intersecting MVector edge = faceVertices[second] - faceVertices[i]; if (planeNormal*edge != 0) { // Perform intersection MVector origin(faceVertices[i]); MPoint originPoint = faceVertices[i]; MVector coord(planePoint); double d = planeNormal*coord; double p = (d - planeNormal*origin) / (planeNormal*edge); // Check if parameter p lies on the edge if (0 <= p && p <= 1) { // output contact point MPoint contact = originPoint + (edge*p); contact *= inputMatrix.inverse(); intersection.append(contact); } } } return intersection; } |
No Comments
There are not comments on this post yet. Be the first one!