global gOrigExplodedHairName try ( destroyDialog RsExplodeHairRollout ) catch () struct RsExplodeHairStruct ( PROP_ORIGINAL_POS = "originalPos", VERTEX_TOLERANCE = 0.005, fn alignPivotToFaceNormal obj faceId = ( convertToMesh obj centerPivot obj resetTransform obj collapseStack obj -- Use to place pivot at face center. local pos = meshop.getFaceCenter obj faceId -- Get a vector using ond of an edge of the face. local verts = ( meshop.getVertsUsingFace obj faceId ) as array local v1 = meshop.getVert obj verts[ 1 ] local v2 = meshop.getVert obj verts[ 2 ] local rVec = normalize ( v1 - v2 ) -- Construct a transform using the face normal and edge. local faceNorm = in coordsys obj ( getFaceNormal obj faceId ) local upVec = normalize ( cross faceNorm rVec ) local mm = matrix3 rVec upVec faceNorm pos local rot = ( inverse mm ) as quat animate off in coordsys local obj.rotation = rot obj.objectoffsetrot *= rot obj.objectoffsetpos *= rot obj.pivot = pos convertTo obj Editable_Poly ), fn compareObjects source target = ( local result = false local tolerance = this.VERTEX_TOLERANCE source.transform = matrix3 1 target.transform = matrix3 1 -- Quickly bail if the number of verts do not match. local numVerts = polyop.getNumVerts source if numVerts != ( polyop.getNumVerts target ) then ( result = false -- Compare vertices and uv's. ) else ( local numMapVerts = polyop.getNumMapVerts source 1 if numMapVerts == ( polyop.getNumMapVerts target 1 ) then ( -- Compare vert positions. local match = true for vertId = 1 to numVerts while match == true do ( local sourceVert = polyop.getVert source vertId local targetVert = polyop.getVert target vertId local vertDistance = distance sourceVert targetVert if vertDistance > tolerance then ( match = false ) ) -- Check UV space. if match then ( for mapVertId = 1 to numMapVerts while match == true do ( local sourceMapVert = polyop.getMapVert source 1 mapVertId local targetMapVert = polyop.getMapVert target 1 mapVertId local vertDistance = distance sourceMapVert targetMapVert if vertDistance > tolerance then ( match = false ) ) ) result = match ) else ( result = false ) ) result ), fn explode obj = ( gOrigExplodedHairName = obj.name undo "Explode Hair" on ( suspendEditing() local pieces = #() while ( polyop.getNumFaces obj ) > 0 do ( local faces = polyop.getElementsUsingFace obj 1 local objName = uniqueName "HairPiece" polyop.detachFaces obj faces asNode:true name:objName local piece = getNodeByName objName if piece != undefined then ( this.alignPivotToFaceNormal piece 1 setUserProp piece this.PROP_ORIGINAL_POS ( piece.transform as string ) append pieces piece ) ) resumeEditing() -- Determine instances within the exploded pieces. local pieceIdx = 1 local numTotalInstances = 0 while ( pieceIdx <= pieces.count ) do ( local currentPiece = pieces[ pieceIdx ] local piecesToDelete = #() for nextPiece in pieces do ( if nextPiece != currentPiece then ( if ( this.compareObjects currentPiece nextPiece ) then ( local nextPieceIdx = findItem pieces nextPiece if nextPieceIdx != 0 then ( local nextPieceTransform = getUserProp nextPiece this.PROP_ORIGINAL_POS setUserProp currentPiece ( "hairPieceInstance_" + nextPiece.name ) nextPieceTransform -- Pop the next piece item from the list. append piecesToDelete nextPiece numTotalInstances += 1 ) ) ) ) for item in piecesToDelete do ( deleteItem pieces ( findItem pieces item ) ) delete piecesToDelete pieceIdx += 1 ) -- Organize the pieces into a grid for easy viewing and editing. local x = #() local y = #() local z = #() for piece in pieces do ( piece.pos = [ 0, 0, 0 ] local size = piece.max - piece.min append x size.x append y size.y append z size.z ) local gridSize = [ amax x, amax y, amax z ] local numPerRow = 10 local xPos = 0 local yPos = 0 local zPos = 0 local currentCol = 1 for objId = 1 to pieces.count do ( pieces[ objId ].pos = [ xPos, yPos, zPos ] xPos += gridSize.x currentCol += 1 if currentCol > numPerRow then ( currentCol = 1 xPos = 0 yPos += gridSize.y ) ) delete obj MessageBox ( "Found a total of " + numTotalInstances as string + " instances." ) title:"Rockstar" ) ), fn reassemble = ( undo "Reassemble Hair" on ( local hairPieces = for obj in objects where ( getUserProp obj this.PROP_ORIGINAL_POS ) != undefined collect obj if( hairPieces.Count == 0 ) then return () hairPieces[ 1 ].transform = execute ( getUserProp hairPieces[ 1 ] this.PROP_ORIGINAL_POS ) for i = 2 to hairPieces.count do ( hairPieces[ i ].transform = execute ( getUserProp hairPieces[ i ] this.PROP_ORIGINAL_POS ) -- Recreate instances local buffer = filterstring ( getUserPropBuffer hairPieces[ i ] ) "\n\r" for item in buffer do ( local data = filterString item "=" local key = data[ 1 ] local val = data[ 2 ] if ( matchPattern key pattern:"hairPieceInstance_*" ignorCase:true ) == true then ( local instancedHairPiece = copy hairPieces[ i ] instancedHairPiece.transform = execute val polyop.attach hairPieces[ 1 ] instancedHairPiece ) ) polyop.attach hairPieces[ 1 ] hairPieces[ i ] ) if gOrigExplodedHairName != undefined then ( hairPieces[ 1 ].name = gOrigExplodedHairName ) ) ), fn clearSceneExplodeProperties = ( for geomObj in geometry do ( geomObjUserPropBuffer = getUserPropBuffer geomObj geomObjUserPropInStream = geomObjUserPropBuffer as stringStream geomObjUserPropOutStream = stringStream "" while not( eof geomObjUserPropInStream ) do ( currentProp = readLine geomObjUserPropInStream if( findString currentProp "originalPos" == Undefined and findString currentProp "hairPieceInstance" == Undefined ) then ( append geomObjUserPropOutStream currentProp append geomObjUserPropOutStream "\n\r" ) ) setUserPropBuffer geomObj (geomObjUserPropOutStream as String) ) ) ) global RsExplodeHair = RsExplodeHairStruct() rollout RsExplodeHairRollout "Explode Hair" ( button btnExplode "Explode Selection" width:180 spinner spTolerance "Instance Tolerance:" range:[ 0.0, 100.0, 0.005 ] type:#float scale:0.0001 width:110 align:#right tooltip:"Tolerance for matching vertices and uv's to determine instances." button btnReassemble "Re-Assemble" width:180 offset:[ 0, 8 ] on btnExplode pressed do ( --We'll clear the user properties on the object as this seems to mess with how the hair gets merged back together. --Transforms possibly get stacked RsExplodeHair.clearSceneExplodeProperties() local objs = selection as array if objs.count == 1 then ( RsExplodeHair.explode objs[ 1 ] ) else ( MessageBox "Select one object!" title:"Rockstar" ) ) on btnReassemble pressed do ( RsExplodeHair.reassemble() --Clear old explode hair attributes RsExplodeHair.clearSceneExplodeProperties() ) on spTolerance changed val do ( RsExplodeHair.VERTEX_TOLERANCE = val ) ) createDialog RsExplodeHairRollout width:200 height:90