-- Utility-functions for use by SkinDataTransfer tool: struct RsSkinDataTransfer ( -- Supplies list of verts adjacent to vertex vertNum, ordered anticlockwise fn adjacentVerts obj vertNum listStartVert: startEdgeNum:1 = ( --local DEBUGDATA = #(#ADJACENTVERTS, obj, vertNum, listStartVert, startEdgeNum) -- Get the faces adjacent to vertNum: local adjFaceNums = polyop.getFacesUsingVert obj vertNum local adjFaceVertsList = #() -- Get these faces' verts adjacent to vertNum, in anticlockwise order local faceVerts, faceVertCount, initialVertIndex, faceVerts, firstAdj, lastAdj, insertAt for faceNum in adjFaceNums collect ( faceVerts = polyop.getFaceVerts obj faceNum faceVertCount = faceVerts.count initialVertIndex = findItem faceVerts vertNum case initialVertIndex of ( 1: ( firstAdj = faceVerts[2] lastAdj = faceVerts[faceVertCount] ) faceVertCount: ( firstAdj = faceVerts[1] lastAdj = faceVerts[faceVertCount - 1] ) default: ( firstAdj = faceVerts[initialVertIndex + 1] lastAdj = faceVerts[initialVertIndex - 1] ) ) faceVerts = #(#(firstAdj, lastAdj)) append adjFaceVertsList faceVerts ) -- Order face-list local vertList, firstVert, lastVert, checkList local listRemoved = #{} listRemoved.count = adjFaceVertsList.count local changed = true while changed do ( changed = false for n = 1 to adjFaceVertsList.count where not listRemoved[n] do ( searchVertList = adjFaceVertsList[n] lastVert = searchVertList[searchVertList.count][2] for m = 1 to adjFaceVertsList.count where (m != n) and not listRemoved[m] do ( checkList = adjFaceVertsList[m] if (checkList[1][1] == lastVert) do ( join searchVertList checkList adjFaceVertsList[m] = undefined listRemoved[m] = true changed = true ) ) ) ) -- Remove empty items left by sorting process: adjFaceVertsList = for item in adjFaceVertsList where (item != undefined) collect item if adjFaceVertsList.count > 1 do ( format "Open?!?\t%\n" (adjFaceVertsList as string) ) -- Output the verts adjacent to vertNum in clockwise order, using the first vert on each adjacent face: outVal = for item in adjFaceVertsList[1] collect item[1] -- Complete open vert-lists: adjFaceVertsList = adjFaceVertsList[1] local lastVert = adjFaceVertsList[adjFaceVertsList.count][2] if (outVal[1] != lastVert) do ( append outVal lastVert ) -- Find requested start of list: if (listStartVert != unsupplied) do ( startEdgeNum = findItem outVal listStartVert ) -- If startEdgeNum is used, rotate edge-order accordingly: if (startEdgeNum > 1) do ( local rotatedVerts = (for n = startEdgeNum to outVal.count collect outVal[n]) join rotatedVerts (for n = 1 to (startEdgeNum - 1) collect outVal[n]) outVal = rotatedVerts ) --format "Rotated:%\n" (outVal as string) outVal ), fn expandTree obj item prevVert: doneVerts:#{} vertInfo:#() expandAll:false = ( local vertNum = item[1] case classOf item[2] of ( UndefinedClass: ( if expandAll or (not doneVerts[vertNum]) do ( doneVerts[vertNum] = true item[2] = for adjVert in (adjacentVerts obj vertNum listStartVert:prevVert) collect #(adjVert, undefined, undefined) vertInfo[vertNum] = item[2] ) ) Array: ( for subItem in item[2] do ( doneVerts[vertNum] = true expandTree obj subItem prevVert:vertNum doneVerts:doneVerts vertInfo:vertInfo expandAll:expandAll ) ) ) ), fn treeHash item = ( if item[2][1][2] == undefined then ( item[2].count ) else ( getHashValue (for subItem in item[2] collect (treeHash subItem)) 0 ) ), fn getUniquePatternHashLevel obj startVert maxPatternDepth:5 = ( -- Increase side of adjacency-tree until only one rotation produces a unique hash local adjVertNums = adjacentVerts obj startVert local rootAdjacencies = for adjVert in adjVertNums collect #(adjVert, undefined) local rootEdgeCount = rootAdjacencies.count local adjacencyTree = #(startVert, rootAdjacencies) local altTrees = for n = 2 to rootEdgeCount collect ( local rotatedEdges = (for m = n to rootEdgeCount collect rootAdjacencies[m]) join rotatedEdges (for m = 1 to (n - 1) collect rootAdjacencies[m]) #(startVert, rotatedEdges) ) local patternDepth = 0 local lastDoneVertsCount local doneVerts = #{} -- Expand the tree out until its first tree-topology is unique, (to avoid upside-down matches or similar), -- all verts have been accounted for (this will happen for radially-symmetrical objects), -- or the maxPatternDepth has been reached (where the selected vert is in a radially-symmetrical region) local patternFound, noMatch, patternHash, altHash while ( (patternDepth < maxPatternDepth) and (lastDoneVertsCount != doneVerts.numberSet) and (patternFound == undefined) ) do ( patternDepth += 1 lastDoneVertsCount = doneVerts.numberSet expandTree obj adjacencyTree doneVerts:doneVerts expandAll:true patternHash = treeHash adjacencyTree --format "% hash 0: %\n" obj.name (patternHash as string) noMatch = true local treeNum = 0 for altTree in altTrees while noMatch do ( treeNum += 1 altHash = treeHash altTree --format "% hash %: %\n" obj.name treeNum (altHash as string) noMatch = (altHash != patternHash) if not noMatch do ( --format "patternDepth %: treeNum % matched: %\n" patternDepth treeNum patternHash ) ) if noMatch do ( patternFound = patternHash ) ) -- If matching-area has radially-symmetrical topology, supply the topmost vert to provide an initial edge to work from -- This will most likely be the top vert of an eyeball, so should be the same on both models. local topVertNum = if noMatch then unsupplied else ( local adjVertPosZs = for adjVert in adjVertNums collect (polyop.getVert obj adjVert).z local upperVertNum = 1 local upperVertZ = adjVertPosZs[1] for n = 2 to adjVertNums.count do ( if (adjVertPosZs[n] > upperVertZ) do ( upperVertZ = adjVertPosZs[n] upperVertNum = n ) ) upperVertNum ) struct RSskinXferPatternHash (patternDepth, patternHash, topVertNum) return RSskinXferPatternHash patternDepth patternHash topVertNum ), fn findPatternTreeMatch obj startVert patternHashData = ( local DEBUGDATA = #(#findPatternTreeMatch, obj, patternHashData) local patternDepth = patternHashData.patternDepth local patternHash = patternHashData.patternHash local sourceTopVert = patternHashData.topVertNum local adjVertNums = adjacentVerts obj startVert local rootAdjacencies = for adjVert in adjVertNums collect #(adjVert, undefined) local rootEdgeCount = rootAdjacencies.count local vertTrees = #(#(startVert, rootAdjacencies)) for n = 1 to patternDepth do ( expandTree obj vertTrees[1] expandAll:true ) for n = 2 to rootEdgeCount do ( local rotatedEdges = (for m = n to rootEdgeCount collect rootAdjacencies[m]) join rotatedEdges (for m = 1 to (n - 1) collect rootAdjacencies[m]) append vertTrees #(startVert, rotatedEdges) ) local foundTreeNum for n = 1 to vertTrees.count while (foundTreeNum == undefined) do ( if ((treeHash vertTrees[n]) == patternHash) do ( foundTreeNum = n ) ) format "TREENUM: %\n" foundTreeNum -- If pattern matches, but has radially-symmetrical topology, a Top Vert will have been provided to match with: if (foundTreeNum != undefined) and (sourceTopVert != unsupplied) do ( -- print "Toppy time!" local adjVertPosZs = for adjVert in adjVertNums collect (polyop.getVert obj adjVert).z local upperVertZ = adjVertPosZs[1] local topVertNum = 1 for n = 2 to adjVertNums.count do ( if (adjVertPosZs[n] > upperVertZ) do ( upperVertZ = adjVertPosZs[n] topVertNum = n ) format "Top vert: %: %\n" foundTreeNum adjVertNums[foundTreeNum] ) -- Use the two meshes' Top Verts to work out which edge-number corresponds with Edge 1 on the source-mesh: foundTreeNum = (mod (adjVertNums.count + topVertNum - sourceTopVert + 1) adjVertNums.count) as integer if (foundTreeNum == 0) do (foundTreeNum = adjVertNums.count) ) -- This number is the edge-number of the target-mesh's pattern-tree that corresponds to the first edge of the source foundTreeNum ), -- Provides a list of all verts sharing a mesh-element with "vert" fn getConnectedVerts obj vert = ( local vertFaces = polyop.getFacesUsingVert obj vert local elementFaces = polyop.getElementsUsingFace obj vertFaces polyop.getVertsUsedOnlyByFaces obj elementFaces ), fn matchObjVerts baseSourceObj baseTargetObj sourceVert targetVert targetStartEdge:1 debugVertMove:false = ( struct vertMatchData (sourceVert, targetVert) local sourceNumVerts = polyop.getNumVerts baseSourceObj local targetNumVerts = polyop.getNumVerts baseTargetObj local sourceAdjVerts = adjacentVerts baseSourceObj sourceVert local targetAdjVerts = adjacentVerts baseTargetObj targetVert startEdgeNum:targetStartEdge local matchArray = #() matchArray.count = sourceNumVerts matchArray[sourceVert] = vertMatchData sourceVert:sourceVert targetVert:targetVert -- Set up initial matched verts local vertsMatched = #{sourceVert} vertsMatched.count = sourceNumVerts for n = 1 to sourceAdjVerts.count do ( matchArray[sourceAdjVerts[n]] = vertMatchData sourceVert:sourceAdjVerts[n] targetVert:targetAdjVerts[n] vertsMatched[sourceAdjVerts[n]] = true ) local unmatchedVerts if debugVertMove do ( for vertNum in vertsMatched do ( polyop.setVert baseTargetObj matchArray[vertNum].targetVert (polyop.getVert baseSourceObj vertNum) node:baseSourceObj --polyop.setVertColor baseTargetObj 0 matchArray[vertNum].targetVert green ) unmatchedVerts = getConnectedVerts baseTargetObj targetVert ) -- Get edge-lists: local sourceEdgeVerts = for edgeNum = 1 to (polyop.getNumEdges baseSourceObj) collect ( (polyop.getEdgeVerts baseSourceObj edgeNum) as bitArray ) local targetEdgeVerts = for edgeNum = 1 to (polyop.getNumEdges baseTargetObj) collect ( (polyop.getEdgeVerts baseTargetObj edgeNum) as bitArray ) local matchedSourceEdges = #{} local matchedTargetEdges = #{} local matchedSourceFaces = #{} local matchedTargetFaces = #{} local edgeMatches = #() edgeMatches.count = sourceEdgeVerts.count -- Function to re-order a face's vert-list, putting two particular verts to the start of the list: fn edgeOrderFace faceVerts vertA vertB = ( local firstVert = case of ( (faceVerts[1] == vertA): ( if (faceVerts[2] == vertB) then 1 else faceVerts.count ) (faceVerts[1] == vertB): ( if (faceVerts[2] == vertA) then 1 else faceVerts.count ) default: ( local findA = findItem faceVerts vertA local findB = findItem faceVerts vertB if (findA < findB) then findA else findB ) ) local newList = (for n = firstVert to faceVerts.count collect faceVerts[n]) join newList (for n = 1 to (firstVert - 1) collect faceVerts[n]) newList ) if not debugVertMove do ( progressStart "Matching verts:" ) local maxVertCount = (getConnectedVerts baseSourceObj sourceVert).count local lastMatched = 0 local notCancelled = true -- Predefine all variables that'll be reused a lot local keepLooking, sourceEdge, targetEdge, sourceEdgeFaces, targetEdgeFaces, sourceFaceVertLists, targetFaceVertLists local searchEdge, sourceVerts, sourceVert, targetVerts, targetVert, targetEdgeNum, newMatchedFaces, checkVert -- Keep looping until loop stops making changes while (lastMatched != vertsMatched.numberSet) \ and (notCancelled = progressUpdate (100.0 * vertsMatched.numberSet / maxVertCount)) \ -- for n = 1 to 1 do ( lastMatched = vertsMatched.numberSet -- MATCH UP EDGES FOR NEWLY-MATCHED VERTS -- local newMatchedEdges = #{} newMatchedEdges.count = sourceEdgeVerts.count -- For unmatched edges... for sourceEdgeNum = 1 to sourceEdgeVerts.count where not matchedSourceEdges[sourceEdgeNum] do ( sourceEdge = sourceEdgeVerts[sourceEdgeNum] -- where a match for its verts has been found... -- (sneaky bitArray matching!) if ((sourceEdge * vertsMatched).numberSet == 2) do ( -- Translate this edge over to the target-vert numbers: searchEdge = (for vertNum in sourceEdge collect matchArray[vertNum].targetVert) as bitArray -- find the matching edge on the target mesh: keepLooking = true for targetEdgeNum = 1 to targetEdgeVerts.count where not matchedTargetEdges[targetEdgeNum] while keepLooking do ( if ((searchEdge - targetEdgeVerts[targetEdgeNum]).numberSet == 0) do ( -- Convert edge-data to arrays, now we're done with checking them as bitArrays edgeMatches[sourceEdgeNum] = targetEdgeNum sourceEdgeVerts[sourceEdgeNum] = sourceEdgeVerts[sourceEdgeNum] as array targetEdgeVerts[targetEdgeNum] = targetEdgeVerts[targetEdgeNum] as array matchedSourceEdges[sourceEdgeNum] = true matchedTargetEdges[targetEdgeNum] = true newMatchedEdges[sourceEdgeNum] = true keepLooking = false ) ) ) ) -- USE THE NEWLY-MATCHED EDGES TO FIND FACES -- for edgeNum in newMatchedEdges do ( -- Get unmatched faces using this source-mesh edge: sourceEdgeFaces = (polyop.getFacesUsingEdge baseSourceObj edgeNum) - matchedSourceFaces if (sourceEdgeFaces.numberSet != 0) do ( sourceEdgeFaces = sourceEdgeFaces as array -- Get unmatched faces using this target-mesh edge: targetEdgeNum = edgeMatches[edgeNum] targetEdgeFaces = ((polyop.getFacesUsingEdge baseTargetObj targetEdgeNum) - matchedTargetFaces) as array sourceVerts = sourceEdgeVerts[edgeNum] targetVerts = targetEdgeVerts[targetEdgeNum] sourceFaceVertLists = for faceNum in sourceEdgeFaces collect (polyop.getFaceVerts baseSourceObj faceNum) targetFaceVertLists = for faceNum in targetEdgeFaces collect (polyop.getFaceVerts baseTargetObj faceNum) -- Order faceLists to put our edge-verts at start of list: -- This allows us to compare vert-order on faces, then find further vert-matches. for n = 1 to sourceEdgeFaces.count do ( sourceFaceVertLists[n] = edgeOrderFace sourceFaceVertLists[n] sourceVerts[1] sourceVerts[2] ) for n = 1 to targetEdgeFaces.count do ( targetFaceVertLists[n] = edgeOrderFace targetFaceVertLists[n] targetVerts[1] targetVerts[2] ) newMatchedFaces = #{} for sourceFaceNum = 1 to sourceFaceVertLists.count do ( -- Find the adjacent face to this edge where the matched verts go in the same direction, -- and use those matched faces to match some more verts sourceVerts = sourceFaceVertLists[sourceFaceNum] checkVert = matchArray[sourceVerts[1]].targetVert keepLooking = true for targetFaceNum = 1 to targetFaceVertLists.count where not newMatchedFaces[targetFaceNum] while keepLooking do ( targetVerts = targetFaceVertLists[targetFaceNum] -- This makes sure the correct face is used of the two possible on this edge if (targetVerts[1] == checkVert) do ( -- Don't do vert-matching for faces with different vert-counts if (sourceVerts.count == targetVerts.count) do ( -- We have a winner! matchedSourceFaces[sourceFaceNum] = true matchedTargetFaces[targetFaceNum] = true newMatchedFaces[targetFaceNum] = true keepLooking = false -- Now we can match up the unmatched verts on this face: for vertNum = 1 to sourceVerts.count do ( sourceVert = sourceVerts[vertNum] if not vertsMatched[sourceVert] do ( vertsMatched[sourceVert] = true matchArray[sourceVert] = vertMatchData sourceVert:sourceVert targetVert:targetVerts[vertNum] if debugVertMove do ( polyop.setVert baseTargetObj targetVerts[vertNum] (polyop.getVert baseSourceObj sourceVert) node:baseSourceObj --polyop.setVertColor baseTargetObj 0 targetVerts[vertNum] green ) ) ) ) ) ) ) ) ) if debugVertMove do ( redrawViews() ) ) if not debugVertMove do ( progressEnd() ) if debugVertMove do ( for item in matchArray where (item != undefined) do (unmatchedVerts[item.targetVert] = false) --polyop.deleteVerts baseTargetObj unmatchedVerts ) if notCancelled then ( local outArray = #(#(),#()) for n = vertsMatched do ( append outArray[1] matchArray[n].sourceVert append outArray[2] matchArray[n].targetVert ) outArray ) else false ), fn getStartVert obj = ( local baseObj = if (isProperty obj #baseObject) then obj.baseObject else obj local vertSel = (polyop.getVertsByFlag baseObj 1) as array local objName = if (isProperty obj #name) then obj.name else "" case of ( (vertSel.count == 0): ( messageBox ("No vertices selected on object " + objName) title:"No verts selected on object" false ) (vertSel.count > 1): ( messageBox ((vertSel.count as string) + " verts selected on object " + objName + "\nPlease select only one!") title:"Too many verts selected on object" false ) default: ( vertSel[1] ) ) ), fn getVertTransferData obj = ( local selObjs = selection as array local baseObj = if (isProperty obj #baseObject) then obj.baseObject else obj local skinMod = obj.modifiers[#skin] if (getCommandPanelTaskMode() != #modify) do (setCommandPanelTaskMode #modify) if (modPanel.getCurrentObject() != skinMod) do (modPanel.setCurrentObject skinMod) local vertData if ((skinOps.GetNumberBones skinMod) == 0) then ( messageBox ((obj as string) + "\nObject's Skin modifier has no bones!") ) else ( local boneCount, boneIDs, boneWeights vertData = for vertNum = 1 to (polyop.getNumVerts baseObj) collect ( boneCount = skinOps.getVertexWeightCount skinMod vertNum boneIDs = for boneNum = 1 to boneCount collect ( skinOps.getVertexWeightBoneId skinMod vertNum boneNum ) boneWeights = for boneNum = 1 to boneCount collect ( skinOps.getVertexWeight skinMod vertNum boneNum ) #(boneIDs, boneWeights) ) ) select selObjs if (vertData == undefined) then (return undefined) else ( struct RSskinXferData (sourcePoly, vertData, transform, skinMod) return RSskinXferData (copy obj.baseObject) vertData obj.transform (copy skinMod) ) ), fn pasteVertTransferData obj copyData sourceStartVert targetStartVert = ( local sourcePoly = copyData.sourcePoly local sourcePatternHash = getUniquePatternHashLevel sourcePoly sourceStartVert local vertData = copyData.vertData obj.transform = copyData.transform local skinMod = obj.modifiers[#skin] if skinMod == undefined do ( skinMod = copy copyData.skinMod addModifier obj skinMod ) local baseObj = obj.baseObject local vertTreeNum = findPatternTreeMatch baseObj targetStartVert sourcePatternHash -- Pattern-match failure: if (vertTreeNum == undefined) do ( return false ) local matchVerts = matchObjVerts sourcePoly baseObj sourceStartVert targetStartVert targetStartEdge:vertTreeNum local sourceVertNums = matchVerts[1] local targetVertNums = matchVerts[2] if (getCommandPanelTaskMode() != #modify) do (setCommandPanelTaskMode #modify) if (modPanel.getCurrentObject() != skinMod) do (modPanel.setCurrentObject skinMod) --skinOps.resetAllBones skinMod local sourceVert, targetVert for n = 1 to sourceVertNums.count do ( sourceVert = sourceVertNums[n] targetVert = targetVertNums[n] local DEBUGDATA = #(#ReplaceVertexWeights, skinMod, n, sourceVert, targetVert, vertData[sourceVert]) skinOps.ReplaceVertexWeights skinMod targetVert vertData[sourceVert][1] vertData[sourceVert][2] ) redrawViews() return true ), fn copyVerts = ( local StartTime = TimeStamp() local objSel = for obj in selection where (isKindOf obj.baseObject Editable_Poly) collect obj case of ( (objSel.count < 2): ( messageBox ("Please select two Editable Poly objects") title:"Not enough suitable objects selected" return false ) (objSel.count > 2): ( messageBox ("Please select only two Editable Poly objects") title:"Too many objects selected" return false ) ) local objA = $[1] local objB = $[2] -- Ensure that both meshes are nice and clean before examining them: polyOp.collapseDeadStructs objA polyOp.collapseDeadStructs objB local objAstartVert = getStartVert objA if objAstartVert == false do return false local objBstartVert = getStartVert objB if objBstartVert == false do return false local patternHash = getUniquePatternHashLevel objA objAstartVert local startEdgeNum = findPatternTreeMatch objB objBstartVert patternHash if (startEdgeNum == undefined) do ( messageBox ("Topology-match not found!") title:"Edges around selected verts are too different" return false ) local matchVerts = matchObjVerts objA objB objAstartVert objBstartVert targetStartEdge:startEdgeNum debugVertMove:true local vertNumsA = matchVerts[1] local vertNumsB = matchVerts[2] format "Matched: %\n" vertNumsA.count for n = 1 to matchVerts[1].count do ( -- polyop.setVert objB vertNumsB[n] (polyop.getVert objA vertNumsA[n]) ) redrawViews() select objB format "\nTook % seconds\n\n" ((TimeStamp() - StartTime) / 1000.0) print matchVerts matchVerts ), -- Called by on viewport-redraw by RStransferSkinDataDrawCallback: fn viewportDraw = ( gw.setTransform (Matrix3 1) local objColours = #(green, red) local objDataArray = ::RsSkinDataTransferRoll.objVertSelData for dataNum = 1 to objDataArray.count do ( local objData = objDataArray[dataNum] local obj = objData.obj if (isValidNode obj) and (not obj.isHidden) do ( local objBase = if isProperty obj #baseObject then obj.baseObject else obj local objColour = objColours[dataNum] local vertNums = objData.elemStartVerts local chooseVert = objData.chooseVert local vertNum, drawPos for n = 1 to vertNums.count where (vertNums[n] != undefined) do ( vertNum = vertNums[n] drawPos = gw.wtranspoint (polyop.getvert objBase vertNum node:obj) + [1,1,0] --gw.wMarker drawPos #asterisk color:white--objColour --gw.wMarker (drawPos + [-1,0,0]) #smallHollowBox color:white--objColour gw.wRect (box2 [drawPos.x - 3, drawPos.y - 3] [drawPos.x + 4, drawPos.y + 3]) objColour gw.wMarker drawPos #circle color:white gw.wtext drawPos (n as string) color:white ) if (chooseVert != undefined) and ((findItem vertNums chooseVert) == 0) do ( drawPos = gw.wtranspoint (polyop.getvert objBase chooseVert node:obj) + [1,1,0] gw.wMarker drawPos #asterisk color:yellow --gw.wMarker drawPos #circle color:white --gw.wtext drawPos (chooseVert as string) color:yellow ) ) ) ), fn hideTheseElements obj verts = ( if isValidNode obj do ( local faceSelectWas = polyop.getFaceSelection obj obj.unhideAll #Face for vertNum in verts where (isKindOf vertNum integer) do ( local vertFaces = polyop.getFacesUsingVert obj vertNum local elementFaces = polyop.getElementsUsingFace obj vertFaces polyop.setFaceSelection obj elementFaces obj.hide #Face ) polyop.setFaceSelection obj faceSelectWas ) ) ) global gRsSkinDataTransfer = RsSkinDataTransfer()