293 lines
7.8 KiB
Plaintext
Executable File
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 |