Below is an example demonstrating the use of the geometry pipeline to convert geometry constructed of adjacent triangles (both visually and in order of facet indices) into geometry constructed of quadrilaterals; reverses the Triangulate operation.
See Also: Geometry Triangulate
// Define an anonymous function; // serves as our main loop, // limits the scope of variables (function(){ // Initialize 'static' variables that hold modifier key state var s_bShiftPressed = false; var s_bControlPressed = false; var s_bAltPressed = false; var s_bMetaPressed = false; // If the "Action" global transient is defined, and its the correct type if( typeof( Action ) != "undefined" && Action.inherits( "DzScriptAction" ) ){ // If the current key sequence for the action is not pressed if( !App.isKeySequenceDown( Action.shortcut ) ){ updateModifierKeyState(); } // If the "Action" global transient is not defined } else if( typeof( Action ) == "undefined" ) { updateModifierKeyState(); } /*********************************************************************/ // void : A function for updating the keyboard modifier state function updateModifierKeyState() { // Get the current modifier key state var nModifierState = App.modifierKeyState(); // Update variables that hold modifier key state s_bShiftPressed = (nModifierState & 0x02000000) != 0; s_bControlPressed = (nModifierState & 0x04000000) != 0; s_bAltPressed = (nModifierState & 0x08000000) != 0; s_bMetaPressed = (nModifierState & 0x10000000) != 0; }; /*********************************************************************/ // void : A function for printing only if debugging function debug() { // If we are not debugging if( !s_bAltPressed ){ // We are done... return; } // Convert the arguments object into an array var aArguments = [].slice.call( arguments ); // Print the array print( aArguments.join(" ") ); }; /*********************************************************************/ // Boolean : A function for testing whether or not a QObject instance // inherits one of a list of types function inheritsType( oObject, aTypeNames ) { // If the object does not define the 'inherits' function if( !oObject || typeof( oObject.inherits ) != "function" ){ // We are done... it is not a QObject return false; } // Iterate over the list of type names for( var i = 0, nTypes = aTypeNames.length; i < nTypes; i += 1 ){ // If the object does not inherit the 'current' type if( !oObject.inherits( aTypeNames[i] ) ){ // Next!! continue; } // Return the result return true; } // Return the result return false; }; /*********************************************************************/ // DzNode : A function for getting the root of a node function getRootNode( oNode ) { // If we have a node and it is a bone if( oNode && inheritsType( oNode, ["DzBone"] ) ){ // We want the skeleton return oNode.getSkeleton(); } // Return the original node return oNode; }; /*********************************************************************/ // DzObject : A function for getting the object for a node function getObjectForNode( oNode, bRoot ) { // Get the node var oContextNode = bRoot ? getRootNode( oNode ) : oNode; // If we do not have a root node if( !oContextNode ){ // We are done... return null; } // Get the object of the root node var oObject = oContextNode.getObject(); // If we do not have an object if( !oObject ){ // We are done... return null; } // Return the object return oObject; }; /*********************************************************************/ // DzShape : A function for getting the shape for a node function getShapeForNode( oNode, bRoot ) { // Get the object of the node var oObject = getObjectForNode( oNode, bRoot ); // If we do not have an object if( !oObject ){ // We are done... return null; } // Get the shape of the root node var oShape = oObject.getCurrentShape(); // If we do not have a shape if( !oShape ){ // We are done... return null; } // Return the shape return oShape; }; /*********************************************************************/ // DzGeometry : A function for getting the geometry for the root of a node function getGeometryForNode( oNode, bRoot, bCached ) { // Get the shape of the root node var oShape = getShapeForNode( oNode, bRoot ); // If we do not have a shape if( !oShape ){ // We are done... return null; } // If we are getting the cached geometry if( bCached ){ // Update the working mesh //oShape.invalidateWorkingMesh(); // Get the object of the root node var oObject = getObjectForNode( oNode, bRoot ); // Return the geometry return oObject.getCachedGeom(); } // Get the geometry of the root node var oGeometry = oShape.getGeometry(); // If we do not have a geometry if( !oGeometry ){ // We are done... return null; } // Return the geometry return oGeometry; }; /*********************************************************************/ // DzFacetMesh : A function for getting the facet mesh for a node function getFacetMeshForNode( oNode, bRoot, bCached ) { // Get the geometry of the node var oGeometry = getGeometryForNode( oNode, bRoot, bCached ); // If we do not have a facet mesh if( !inheritsType( oGeometry, ["DzFacetMesh"] ) ){ // We are done... return null; } // Return the geometry return oGeometry; }; /*********************************************************************/ // Boolean : A function for quickly determining if a number is odd function isOdd( nNum ) { // Return whether or not the last bit is set; // true == odd, false == even return (nNum & 0x01) != 0; }; /*********************************************************************/ // Array : A function for weeding out non-unique elements of an array function uniqueElements( aArray, nColumn ) { // Create an object to track which values already exist var oMap = {}; // Filter the array using the value of each element // converted to a string as the key into the map return aArray.filter( function( vValue ) { // Convert the value into a string var sKey = Array.isArray( vValue ) && typeof( nColumn ) == "number" ? JSON.stringify( vValue[ nColumn ] ) : JSON.stringify( vValue ); // If the outer map already has the 'current' element, it is not unique return oMap.hasOwnProperty( sKey ) ? false : oMap[ sKey ] = true; } ); }; /*********************************************************************/ // Array : A function for getting the intersection of two arrays function intersection( aArrayA, aArrayB ) { return aArrayA.filter( function( vValue ){ return aArrayB.indexOf( vValue ) > -1; } ); }; /*********************************************************************/ // Array : A function for integrating the values of one array with another // array of the same length; adds a dimension to an array function appendColumn( aArrayA, aArrayB ) { if( aArrayA.length != aArrayB.length ){ throw( "Arrays passed to appendColumn() differ in length." ); return aArrayA; } return aArrayA.map( function( vValue, nIndex ){ return [ vValue, aArrayB[ nIndex ] ]; } ); }; /*********************************************************************/ // Array : A function for extracting a column of values from a 2D array function extractColumn( aArray, nColumn ) { // Extract a column of values from the array return aArray.map( function( vValue ){ return vValue[ nColumn ]; } ); }; /*********************************************************************/ // Get the primary selection var oNode = getRootNode( Scene.getPrimarySelection() ); // If nothing is selected if( !oNode ){ // We are done... return; } // Get the mesh of the root node var oMesh = getFacetMeshForNode( oNode, true, false ); // If we do not have a mesh if( !oMesh ){ // We are done... return; } // Get the number of facets in the mesh var nFacets = oMesh.getNumFacets(); // Must have even number of facets if( isOdd( nFacets ) ){ // Provide feedback print( "Odd facet count in mesh:", oNode.name ); // We are done... return; } // Decalre working variable var oFaceGroup; // Get the number of face groups var nFaceGroups = oMesh.getNumFaceGroups(); // Pre-size the array of face group names var aFaceGroups = new Array( nFaceGroups ); // Iterate over the face groups for( var i = 0; i < nFaceGroups; i += 1 ){ // Get the 'current' face group oFaceGroup = oMesh.getFaceGroup( i ); // If the number of facets in the face group is an odd number if( isOdd( oFaceGroup.count() ) ){ // Provide feedback print( "Odd facet count in face group:", oFaceGroup.name ); // We are done... return; } // Capture the face group name aFaceGroups[ i ] = oFaceGroup.name; } // Decalre working variable var oSurface; // Get the number of surfaces var nSurfaces = oMesh.getNumMaterialGroups(); // Pre-size the array of surface names var aSurfaceNames = new Array( nSurfaces ); // Iterate over the surfaces for( var i = 0; i < nSurfaces; i += 1 ){ // Get the 'current' surface oSurface = oMesh.getMaterialGroup( i ); // If the number of facets in the surface is an odd number if( isOdd( oSurface.count() ) ){ // Provide feedback print( "Odd facet count in surface:", oSurface.name ); // We are done... return; } // Capture the surface name aSurfaceNames[ i ] = oSurface.name; } // Decalre working variables var oFacetA, oFacetB; var aPairsA, aPairsB, aUniquePairs; var aVertsA, aVertsB, aVerts, aUVsA, aUVsB, aUVs; // Initialize the facet data index var nFacetDataIdx = 0; // Pre-size the array of facet data objects var aFacetDatas = new Array( nFacets / 2 ); // Iterate over all the facets, in pairs for( var i = 0; i < nFacets; i += 2 ){ // Get the 'current' facet oFacetA = oMesh.getFacet( i ); // If the facet is not a triangle if( !oFacetA.isTri() ){ // Provide feedback print( "Facet", i, "is not a triangle!" ); // We are done... return; } // Get the 'next' facet; assumed pair oFacetB = oMesh.getFacet( i + 1 ); // If the facet is not a triangle if( !oFacetB.isTri() ){ // Provide feedback print( "Facet", i + 1, "is not a triangle!" ); // We are done... return; } // Build the vertex/UV index pairs for the first triangle aVertsA = [ oFacetA.vertIdx1, oFacetA.vertIdx2, oFacetA.vertIdx3 ]; aUVsA = [ oFacetA.uvwIdx1, oFacetA.uvwIdx2, oFacetA.uvwIdx3 ]; aPairsA = appendColumn( aVertsA, aUVsA ); // Build the vertex/UV index pairs for the second triangle aVertsB = [ oFacetB.vertIdx1, oFacetB.vertIdx2, oFacetB.vertIdx3 ]; aUVsB = [ oFacetB.uvwIdx1, oFacetB.uvwIdx2, oFacetB.uvwIdx3 ]; aPairsB = appendColumn( aVertsB, aUVsB ); // Provide feedback debug( i, ":", aPairsA ); debug( i + 1, ":", aPairsB ); // Get the unique vertex/UV index pairs aUniquePairs = uniqueElements( aPairsA.concat( aPairsB ), 0 ); // If we do not have 4 vertices if( aUniquePairs.length != 4 ){ // Provide feedback print( "Invalid vertex count:", aUniquePairs.length ); // We are done... return; } // If the shift modifier is not pressed and the winding order of the facets is not the same if( !s_bShiftPressed && (aVertsA[ 0 ] != aVertsB[ 0 ] || aVertsA[ 2 ] != aVertsB[ 1 ]) ){ // Provide feedback print( "Winding order mismatch:", i, i + 1 ); // We are done... return; } aVerts = extractColumn( aUniquePairs, 0 ); aUVs = extractColumn( aUniquePairs, 1 ); // Capture the facet data aFacetDatas[ nFacetDataIdx ] = { "v": aVerts, "uv": aUVs, "g": oFacetA.faceGroupIndex, "m": oFacetA.materialIndex }; // Provide feedback debug( "#", nFacetDataIdx, ":", JSON.stringify( aFacetDatas[ nFacetDataIdx ], null, "\t" ) ); // Increment the index nFacetDataIdx += 1; } // Get the current shape for the node var oShape = getShapeForNode( oNode, true ); // Get the working geometry for the current shape var oGeometry = oShape.getCurrentGeometry( DzShape.WorkingGeometry ); // Switch the mesh we are using to a copy of the working geometry - do not share vertices or facets; // we only want to modify the mesh for the selected node, not every instance in the scene oMesh = oGeometry.makeCopy( false, false ); // If the mesh does not inherit DzFacetMesh if( !oMesh.inherits( "DzFacetMesh" ) ){ // We are done... return; } // Declare working variable var oUVmap; // Define whether or not we want a new facet set var bNewFacetSet = false; // If we want a new facet set if( bNewFacetSet ){ // Get the current UV map oUVmap = oMesh.getUVs(); // Create a new facet set (LOD); capture the index var nFacetSet = oMesh.createFacetSet( "Quads from Tris" ); // If the active facet set is not our new one if( oMesh.getActiveFacetSet() != nFacetSet ){ // Make our facet set the active one oMesh.setActiveFacetSet( nFacetSet ); } } // Start editing the mesh oMesh.beginEdit(); // If we do not want a new facet set if( !bNewFacetSet ){ // Remove all existing facets; keep surfaces and face groups oMesh.removeAllFacets( false, false ); // Remove all existing UV sets oMesh.removeAllUVSets(); // If we want a new facet set } else { // Now that our facet set is created, // cause the existing UV map to be used oMesh.setUVList( oUVmap ); // Iterate over the list of face groups for( var i = 0; i < nFaceGroups; i += 1 ){ // Create the 'current' face group for our facet set oMesh.createFaceGroup( aFaceGroups[ i ] ); } // Iterate over the list of surfaces for( var i = 0; i < nSurfaces; i += 1 ){ // Create the 'current' surface for our facet set oMesh.createMaterialGroup( aSurfaceNames[ i ] ); } } // Declare working variable var oFacetData, oFacet; // Get the number of facet data objects var nNewFacets = aFacetDatas.length; // Pre-size the number of facets for our facet set oMesh.preSizeFacets( nNewFacets ); // Iterate over the list of facet data objects for( var i = 0; i < nNewFacets; i += 1 ){ // Get the 'current' facet data object oFacetData = aFacetDatas[ i ]; // Create a new facet oFacet = new DzFacet(); // Assign vertex indices oFacet.vertIdx1 = oFacetData[ "v" ][ 0 ]; oFacet.vertIdx2 = oFacetData[ "v" ][ 1 ]; oFacet.vertIdx3 = oFacetData[ "v" ][ 2 ]; oFacet.vertIdx4 = oFacetData[ "v" ][ 3 ]; // Assign normal indices oFacet.normalIdx1 = oFacetData[ "v" ][ 0 ]; oFacet.normalIdx2 = oFacetData[ "v" ][ 1 ]; oFacet.normalIdx3 = oFacetData[ "v" ][ 2 ]; oFacet.normalIdx4 = oFacetData[ "v" ][ 3 ]; // Assign UV indices oFacet.uvwIdx1 = oFacetData[ "uv" ][ 0 ]; oFacet.uvwIdx2 = oFacetData[ "uv" ][ 1 ]; oFacet.uvwIdx3 = oFacetData[ "uv" ][ 2 ]; oFacet.uvwIdx4 = oFacetData[ "uv" ][ 3 ]; // Assign face group oMesh.activateFaceGroup( oFacetData[ "g" ] ); // Assign surface oMesh.activateMaterial( oFacetData[ "m" ] ); // Add the facet to the mesh (for our facet set) oMesh.addFacet( oFacet ); } // We are done editing the mesh oMesh.finishEdit(); // Set the facet mesh oShape.setFacetMesh( oMesh ); // Finalize the function and invoke })();