Files
2025-09-29 00:52:08 +02:00

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)