filein "rockstar/export/settings.ms" -- This is fast -- Figure out the project theProjectRoot = RsConfigGetProjRootDir() theProject = RSConfigGetProjectName() theWildWest = RsConfigGetWildWestDir() theProjectConfig = RsConfigGetProjBinConfigDir() toolsRoot = RsConfigGetToolsRootDir() -- filein (RsConfigGetWildWestDir() + "script\\max\\Rockstar_North\\character\\Includes\\FN_Rigging.ms") filein (theWildWest + "script/3dsMax/_config_files/Wildwest_header.ms") filein (theWildWest + "script/3dsMax/_common_functions/FN_RSTA_UI.ms") filein (theWildWest + "script/3dsMax/_common_functions/FN_RSTA_Numeric.ms") fn RSTA_getVertNormal obj vertToTest = ( /** GET THE VERTX NORMAL OF VERTtOtEST **/ local vertFaces = polyOp.getFacesUsingVert obj vertToTest local tNormal = [0,0,0]; for i in vertFaces do tNormal += polyOp.getFaceNormal obj i local t02 = normalize (tNormal / vertFaces.numberSet) return t02 ) fn RSTA_reNomalizeSkinWeights vertexId skinMod = ( skinOps.unNormalizeVertex skinMod vertexID false ) --if the vert has already been weighted tyhen the denormalizing will nacker up so we have to be a bit funky here! fn RSTA_deNormalizeSkinWeights vertexID skinMod = ( skinOps.unNormalizeVertex skinMod vertexID false skinOps.SetVertexWeights skinMod vertexID 1 1.0 skinOps.unNormalizeVertex skinMod vertexID true skinOps.SetVertexWeights skinMod vertexID 1 0.0 ) FN RSTA_queryPasteSkinVert obj copyFromvert pasteTovert boneList skinMod = ( /** WILL COPY THE VERTEX SKIN WEIGHT FROM COPYfROMvERT TO PASTEtOvERT **/ RSTA_deNormalizeSkinWeights pasteTovert skinMod -- now we set all influences for every bone to zero bCount = boneList[1].count for b = 1 to bCount do ( skinOps.SetVertexWeights skinMod pasteTovert b 0.0 ) noOfInfluences = skinOps.GetVertexWeightCount skinMod copyFromvert for inf = 1 to noOfInfluences do ( --get the id of the current bone influence affectedBones = skinOps.GetVertexWeightBoneID skinMod copyFromvert inf --debugPrint ("affectedBones:"+(affectedBones as string)) --get the influence of the current bone influence infForCurrBone = skinOps.getvertexWeight skinMod copyFromvert inf skinOps.SetVertexWeights skinMod pasteTovert affectedBones infForCurrBone ) RSTA_reNomalizeSkinWeights pasteTovert skinMod ) fn RSTA_intersectionTest rm targetMesh sourceMesh masterFromArray masterToArray vertMappingArray theFacesArray theVertArray testVert theNormal = ( local theIndex = rm.getClosestHit () --get the index of the closest hit by the ray local theFace = rm.getHitFace theIndex --get the face index corresponding to that indexed hit append theFacesArray theFace --add to the face array to select the face... --now get the verts from theFace local faceVerts = (meshop.getVertsUsingFace targetMesh theFace as array) local vertToUse = undefined --MASSIVE HACK! --what i will try here is to find the closest vert on faceVerts positioonally to testVert local thePos = in coordsys world getVert sourceMesh testVert --get the position of the vertex vDist = 100000000 for fv in faceVerts do ( local fvPos = in coordsys world getVert targetMesh fv if (distance thePos fvPos) < vDist do ( vDist = (distance thePos fvPos) vertToUse = fv ) ) --END MASSIVE HACK local thePos = in coordsys world getVert sourceMesh testVert --get the position of the vertex append theVertArray vertToUse --now find the corresponding masterMeshVert for testVert via masterFromArray local masterCopyFromVert = findItem masterFromArray testVert if masterCopyFromVert != 0 then ( --now find the corresponding masterMeshVert vertTouse via the masterToArray local masterCopyToVert = findItem masterToArray vertToUse if masterCopyToVert != 0 then ( --format ("From: "+(masterCopyFromVert as string)+" To: "+(masterCopyToVert as string)+"\n") if flipNormal == true then ( append vertMappingArray[1] masterCopyFromVert append vertMappingArray[2] masterCopyToVert ) else ( append vertMappingArray[2] masterCopyFromVert append vertMappingArray[1] masterCopyToVert ) ) else ( format ("No masterCopyToVert found for vert "+(vertToUse as string)+" on TO Mesh.\n") ) ) else ( format ("No masterCopyFromVert found for vert "+(testVert as string)+" on FROM Mesh.\n") ) return vertMappingArray ) fn RSTA_barycentricHitTest masterMesh sourceMesh targetMesh flipNormal = ( rollout progBarDouble "Vert Matching..." ( /** --rollout for progress bar **/ progressbar progB_A pos:[10,10] color:red progressbar progB_B pos:[10,25] color:yellow ) if ((progBarDouble != undefined) and (progBarDouble.isDisplayed)) do (destroyDialog progBarDouble) CreateDialog progBarDouble width:300 Height:45 --first off build mapping between the real none split mesh and the form and to meshes local masterMeshVertCount = masterMesh.numverts local masterFromArray = #() local masterToArray = #() local targetMeshVertCount = targetMesh.numverts local sourceMeshVertCount = sourceMesh.numverts local tol = 0.0001 for mmV = 1 to masterMeshVertCount do ( --thisData = #(undefined,undefined) local mmvPos = in coordsys world getVert masterMesh mmV --get the position of the vertex --thisData[1] = mmV local thisDatasourceMesh = undefined for fmV = 1 to sourceMeshVertCount do ( local fromVertPos = in coordsys world getVert sourceMesh fmV if distance mmvPos fromVertPos <= tol do ( --so this is the vert on the targetMesh that shares the position of the vert on sourceMesh thisDatasourceMesh = fmV --format ("(FROM) mmV: "+(mmV as string)+" matches "+(fmV as string)+"\n") ) ) append masterFromArray thisDatasourceMesh progBarDouble.progB_A.value = (100.*mmV/masterMeshVertCount) local thisDatatargetMesh = undefined for tmV = 1 to targetMeshVertCount do ( local toVertPos = in coordsys world getVert targetMesh tmV if distance mmvPos toVertPos <= tol do ( --so this is the vert on the targetMesh that shares the position of the vert on sourceMesh thisDatatargetMesh = tmV --format ("(TO) mmV: "+(mmV as string)+" matches "+(tmV as string)+"\n") ) ) append masterToArray thisDatatargetMesh progBarDouble.progB_B.value = (100.*mmV/masterMeshVertCount) ) destroyDialog progBarDouble if ((progBar != undefined) and (progBar.isDisplayed)) do (destroyDialog progBar) CreateDialog progBar width:300 Height:30 --now do the hit testing local theFacesArray = #() --init. an array to collect face selection local theVertArray = #() --init an array to collect hit verts local vertMappingArray = #( #(), --copyFromVert #() --copyTovert ) rm = RayMeshGridIntersect () --create an instance of the Reference Target rm.Initialize 10 --init. the voxel grid size to 10x10x10 rm.addNode targetMesh --add the sphere to the grid rm.buildGrid () --build the grid data (collecting faces into the grid voxels) local missedVerts = #() --init an array for all verts that are missed if flipNormal == false then ( format ("Using standard normal direction from "+sourceMesh.name+"\n") ) else ( format ("Using flipped normal direction from "+sourceMesh.name+"\n") ) for testVert = 1 to sourceMeshVertCount do --go through all verts of the Geosphere ( local thePos = in coordsys world getVert sourceMesh testVert --get the position of the vertex local theNormal = -(getNormal sourceMesh testVert) --get the normal of the vertex, reverse direction if flipNormal == false then ( theNormal = (getNormal sourceMesh testVert) --get the normal of the vertex, dont reverse direction ) local theHitsCount = rm.intersectRay thePos theNormal false --intersect the ray with the targetMesh (final argument treats it as double sided if true) if theHitsCount < 1 then ( --ok we will bend the normal a bit till we find a hit for sample = 1 to 64 do ( --should i keep sampling until ive got somethign like 3 hits and find the best? if theHitsCount < 1 then ( --format ("Vert: "+(testVert as string)+" Sampling: "+(sample as string)+"\n") randVal = random 0.01 0.015 theNormal = theNormal + randVal newTheHitsCount = rm.intersectRay thePos theNormal false if newTheHitsCount > 0 then ( format ("Vert: "+(testVert as string)+" Sampling: "+(sample as string)+"\n") theHitsCount = newTheHitsCount ) ) ) ) else ( ) if theHitsCount > 0 then --if have hit anything... ( vertMappingArray = RSTA_intersectIonTest rm targetMesh sourceMesh masterFromArray masterToArray vertMappingArray theFacesArray theVertArray testVert theNormal ) else ( format "The Ray % Missed\n" testVert append missedVerts testVert ) progBar.prog.value = (100.*testVert/sourceMeshVertCount) ) rm.free if missedVerts.count != 0 do ( local ms = mesh_select() --create a mesh select modifier addModifier masterMesh ms --add on top of the sphere select masterMesh --select the sphere max modify mode --go to modify panel setVertSelection masterMesh 1 missedVerts --set the selection in the MeshSelect subObjectLevel = 1 --go to vert selection level -- setFaceSelection targetMesh 1 theFacesArray --set the selection in the MeshSelect -- subObjectLevel = 3 --go to face selection level ) return vertMappingArray ) fn RSTA_transferOuterShellSkinToInner masterObject hairMatId flipNormal = ( --what we'll do is take the mesh with the shell --then duplicate it --detach the inner shell fom this dup via material id --use the detached inner as the target mesh --then when e do the mapping in the result phase we dont use this detached we use the real object --we also have to ensure that we dont use any verts in the original mesh whihc have material id matching the hair shell local start = timestamp() local nnl = undefined maxOps.cloneNodes masterObject cloneType:#copy newNodes:&nnl select nnl local masterMesh = $ masterMesh.name = (masterObject.name+"_MasterMesh") collapseStack masterMesh convertToPoly(masterMesh) local nnl = undefined maxOps.cloneNodes masterMesh cloneType:#copy newNodes:&nnl select nnl local sourceMesh = $ sourceMesh.name = (masterObject.name+"_sourceMesh") collapseStack sourceMesh --now we need to find the faces in cloneObj which have the hairMatId local totalFaces = polyop.getNumFaces sourceMesh local hairFaces = #() local noneHairFaces = #() for face = 1 to totalFaces do ( local thisFaceId = polyop.getFaceMatID sourceMesh face if thisFaceId == hairMatId then ( append hairFaces face ) else ( append noneHairFaces face ) ) format ((hairFaces.count as string)+" total hair faces\n") format ((noneHairFaces.count as string)+" total NONE hair faces\n") local hairMeshName = (masterObject.name+"_targetMesh") local targetMesh = polyop.detachFaces sourceMesh hairFaces delete:true asNode:true name:hairMeshName node:sourceMesh local targetMesh = getNodeByName hairMeshName convertTo sourceMesh TriMeshGeometry if flipNormal == false then ( meshop.flipNormals sourceMesh #{1..sourceMesh.numfaces} ) convertTo targetMesh TriMeshGeometry convertTo masterMesh TriMeshGeometry local skinMappIngArray = #(#(),#()) --this will have multiple 2 index pairs - one is an error message to print out and the 2nd the matching errored vert number local totalVerts = #() for face in noneHairFaces do ( local thisFaceVerts = polyop.getVertsUsingFace masterObject face for vert in thisFaceVerts do ( appendIfUnique totalVerts vert ) ) format ("total verts in "+masterObject.name+": "+(totalverts.count as string)+"\n") rollout progBar "Progress..." ( /** --rollout for progress bar **/ progressbar prog pos:[10,10] color:red ) if ((progBar != undefined) and (progBar.isDisplayed)) do (destroyDialog progBar) CreateDialog progBar width:300 Height:30 local vertexMapping = RSTA_barycentricHitTest masterMesh targetMesh sourceMesh flipNormal format ("vertexMapping[1].count: "+(vertexMapping[1].count as string)+"\n") select masterObject max modify mode skinMod = masterObject.modifiers[#Skin] boneList = #(#(),#()) totalBones = skinOps.GetNumberBones skinMod for b = 1 to totalBones do ( append boneList[1] b --appends bone index currentBonename = skinOps.getBoneName skinMod b 0 append boneList[2] currentBonename ) --now do the skin transfering progbar.prog.color = green for vert = 1 to vertexMapping[1].count do ( copyFromvert = vertexMapping[1][vert] pasteTovert = vertexMapping[2][vert] --format ("Attempting to transfer weight from "+(copyFromvert as string)+" to "+(pasteTovert as string)+"\n") RSTA_queryPasteSkinVert masterObject copyFromvert pasteTovert boneList skinMod progBar.prog.value = (100.*vert/vertexMapping[1].count) ) destroyDialog progBar delete sourceMesh delete targetMesh delete masterMesh format "Mapping complete.\n" local end = timeStamp() format "Processing RSTA_transferOuterShellSkinToInner took % seconds\n" ((end - start) / 1000.0) ) --////////////////////////////////////////////////////////////////////////////////////////////// -- UI --////////////////////////////////////////////////////////////////////////////////////////////// if skinTransferToHairShell_UI != undefined then destroyDialog skinTransferToHairShell_UI rollout skinTransferToHairShell_UI "Hair Transfer" width:200 ( -- BANNER -------------------------------------------------------- dotNetControl rsBannerPanel "System.Windows.Forms.Panel" height:32 pos:[0,0] width:skinTransferToHairShell_UI.width --https://devstar.rockstargames.com/wiki/index.php/HairSkinTransfer local banner = makeRsBanner dn_Panel:rsBannerPanel wiki:"HairSkinTransfer" filename:(getThisScriptFilename()) --////////////////////////////////////////////////////////////////////////////// -- VARIABLES --////////////////////////////////////////////////////////////////////////////// local imagePath = (theWildWest+"script/3dsMax/Characters/Rigging/mrSkeleton_v2/images/") local seppBtn_Img = #( (imagePath+"seppBtn_Img.bmp") ) local flipNormal = false local masterObject = undefined local hairMatId = undefined --////////////////////////////////////////////////////////////////////////////// -- FUNCTIONS --////////////////////////////////////////////////////////////////////////////// --////////////////////////////////////////////////////////////////////////////// -- CONTROLS --////////////////////////////////////////////////////////////////////////////// pickbutton btn_PickSourceMesh "Pick Mesh" width:180 height:30 tooltip:"Pick mesh" autoDisplay:true checkbox chk_flipNormal "Flip Normal" tooltip:"Shoot ray inwards or outwards" pos:[20,70] spinner spn_hairmatId "Hair Id" tooltip:"Material id of hair mesh on object" pos:[115,70] type:#integer range:[1,100,4] fieldwidth:30 button btn_TransferSkinToHair "Transfer skin from outer shell to Hair" pos:[10,90] width:180 height:30 tooltip:"Transfer skin from outer shell to faces with Hair Id" --////////////////////////////////////////////////////////////////////////////// -- EVENTS --////////////////////////////////////////////////////////////////////////////// on skinTransferToHairShell_UI open do ( banner.setup() flipNormal = chk_flipNormal.state hairMatId = spn_hairmatId.value ) on btn_PickSourceMesh picked obj do ( masterObject = undefined --reset it if superClassOf obj.baseObject == GeometryClass do ( format ("You picked "+obj.name+"\n") masterObject = obj ) ) on chk_flipNormal changed theState do ( flipNormal = chk_flipNormal.state ) on spn_hairmatId changed val do ( hairMatId = spn_hairmatId.value ) on btn_TransferSkinToHair pressed do ( format ("flipNomral: "+(flipNormal as string)+"\n") format ("hairMatId: "+(hairMatId as string)+"\n") if masterObject != undefined then ( RSTA_transferOuterShellSkinToInner masterObject hairMatId flipNormal ) else ( format ("No valid source mesh picked.\n") ) ) ) createDialog skinTransferToHairShell_UI width:200 height:125 style:#(#style_titlebar, #style_border, #style_sysmenu)