User Tools

Site Tools


Geometry Detriangulate

Summary

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

API Areas of Interest

Example

Geometry_Detriangulate.dsa
// 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
})();