540 lines
17 KiB
Plaintext
Executable File
540 lines
17 KiB
Plaintext
Executable File
|
|
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)
|