Files
gtav-src/tools_ng/wildwest/script/3dsMax/Characters/explodeHair.ms
T
2025-09-29 00:52:08 +02:00

293 lines
7.8 KiB
Plaintext
Executable File

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