575 lines
18 KiB
Plaintext
Executable File
575 lines
18 KiB
Plaintext
Executable File
--
|
|
-- File:: cloth.ms
|
|
-- Description:: Cloth support functions.
|
|
--
|
|
-- Author:: David Muir <david.muir@rockstarnorth.com>
|
|
-- Date:: 24 April 2009
|
|
--
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Uses
|
|
-----------------------------------------------------------------------------
|
|
filein "rockstar/util/collutil.ms"
|
|
filein "rockstar/util/material.ms"
|
|
filein "pipeline/helpers/cloth_helpers.ms"
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Globals
|
|
-----------------------------------------------------------------------------
|
|
-- None (yet)
|
|
-- Channel indices may go here.
|
|
--
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Struct
|
|
-----------------------------------------------------------------------------
|
|
|
|
struct RsClothStruct
|
|
(
|
|
|
|
bakevalue = [1,1,1],
|
|
channelMap = "(pin|10|9|0.0;pinradius|11|21|0.0;pinramp|12|22|0.0;edgecomp|13|23|0.25;bslength|14|24|0.5;bsstr|15|25|0.7;weight|16|10|0.0;mapTolerance|17|26|0.001;mapError|18|27|0.001;dynPin|19|13|0;inflationScale|20|15|1.0;customEdge|21|16|0.0)",
|
|
|
|
----------------------------------------------------------------------------------------------
|
|
-- general cloth facuntions
|
|
----------------------------------------------------------------------------------------------
|
|
--
|
|
-- name: IsCloth
|
|
-- desc: Return whether an object is flagged as being cloth.
|
|
--
|
|
fn IsCloth obj = (
|
|
if ( "Gta Object" != GetAttrClass obj ) then
|
|
return ( false )
|
|
|
|
local idxIsCloth = ( GetAttrIndex "Gta Object" "Is Cloth" )
|
|
return (GetAttr obj idxIsCloth)
|
|
),
|
|
|
|
--
|
|
-- Set to minimal exported channel amount
|
|
--
|
|
fn ditchChannel obj intafaze channelIndex =
|
|
(
|
|
if intafaze.getVDataChannelSupport obj channelIndex then
|
|
(
|
|
intafaze.freeVData obj channelIndex
|
|
intafaze.setVDataChannelSupport obj channelIndex false
|
|
)
|
|
),
|
|
fn SetClothMapsSupport obj ditchChannelNums =
|
|
(
|
|
local intafaze = undefined
|
|
case classof obj of
|
|
(
|
|
Editable_Mesh:(intafaze = meshop)
|
|
Editable_Poly:(intafaze = polyop)
|
|
PolyMeshObject:(intafaze = polyop)
|
|
Tripatch:(intafaze = patch)
|
|
)
|
|
if undefined!=intafaze then
|
|
(
|
|
local skinSave = obj.modifiers[#Skin]
|
|
if undefined!=skinSave then
|
|
deleteModifier obj skinSave
|
|
|
|
for dc in ditchChannelNums do
|
|
ditchChannel obj intafaze RAGEClothChannelTable[dc].vDataChn
|
|
|
|
if undefined!=skinSave then
|
|
addModifier obj skinSave
|
|
)
|
|
else
|
|
gRsUlog.LogWarning "Counldn't recognise correct interface class for cloth mesh object!" context:obj
|
|
),
|
|
--
|
|
-- name: IsValidCloth
|
|
-- desc: Return whether an object is valid (Less verts than limit)
|
|
--
|
|
fn IsValidCloth obj =
|
|
(
|
|
local isValidClothMsg = "IsValidCloth" + (obj.name as string)
|
|
gRsUlog.ProfileStart isValidClothMsg
|
|
|
|
|
|
local vertLimit = 1000
|
|
if (obj.numVerts > vertLimit) then
|
|
(
|
|
throw (obj.name + " - Cloth (simulation) mesh has too many verts.")
|
|
)
|
|
if MultiMaterial==(classof obj.material) then
|
|
(
|
|
local mapList = #()
|
|
RsMatGetMapIdsUsedOnObject obj mapList
|
|
if mapList.count!=1 then
|
|
(
|
|
throw (obj.name + " - Cloth (simulation) mesh has more than one material.")
|
|
)
|
|
local arrayindex = finditem obj.material.materialIDList mapList[1]
|
|
local mtl = obj.material.materiallist[arrayindex]
|
|
if undefined==mtl then
|
|
throw (obj.name + " - Cloth (simulation) mesh material is undefined. Empty slot?")
|
|
if (RstGetIsTwoSided mtl) then
|
|
throw (obj.name + " - Cloth (simulation) mesh material must not have two sided flag.")
|
|
)
|
|
else if Rage_Shader==(classof obj.material) then
|
|
(
|
|
local mtl = obj.material
|
|
if (RstGetIsTwoSided mtl) then
|
|
throw (obj.name + " - Cloth (simulation) mesh material must not have two sided flag.")
|
|
)
|
|
|
|
-- hierarchy transform checks
|
|
-- local allClothGroupMeshes
|
|
-- if RsLodDrawable_HasRenderModel obj then
|
|
-- (
|
|
-- local renderModel = (RsLodDrawable_GetRenderModel obj)
|
|
-- append renderModelName renderModel.name
|
|
-- )
|
|
local savesel = selection as array
|
|
local groupCount = 0
|
|
local clothMeshes = RsLodDrawable_SetUserProps obj &groupCount hasCloth:true clothTypeGroup:undefined -- clothTypeGroup undefined to prevent user prop setting
|
|
print ("clothMeshes:"+clothMeshes as string)
|
|
for cm in clothMeshes do
|
|
(
|
|
if not RsCompareMatrix obj.transform cm.transform translation:false then
|
|
throw ("cloth mesh "+cm.name+" has different rotation as "+obj.name+". Please fix.")
|
|
)
|
|
select savesel
|
|
|
|
-- this has the cloth tag, so is the sim mesh or the only one.
|
|
if not hasAnyPinnedVerts obj then
|
|
throw ("Cloth mesh "+obj.name+" has no pinned vertices!")
|
|
|
|
gRsUlog.ProfileEnd context:isValidClothMsg
|
|
),
|
|
|
|
|
|
fn bakeVertexChannels objs =
|
|
(
|
|
if (classof objs) != Array then
|
|
(
|
|
objs = #(objs)
|
|
)
|
|
|
|
for obj in objs do
|
|
(
|
|
local interfaze = undefined
|
|
case classof obj of
|
|
(
|
|
Editable_mesh:(interfaze = meshop;)
|
|
Editable_Poly:(interfaze = polyop;)
|
|
PolyMeshObject:(interfaze = polyop;)
|
|
)
|
|
|
|
try
|
|
(
|
|
for channelindex = -2 to 0 do
|
|
(
|
|
if interfaze.getmapsupport obj channelindex then
|
|
(
|
|
local average = (color 0 0 0)
|
|
local numverts = interfaze.getnummapverts obj channelindex
|
|
for vertindex = 1 to numverts do
|
|
average = average + interfaze.getmapvert obj channelindex vertindex
|
|
average = average/numverts
|
|
for vertindex = 1 to numverts do
|
|
interfaze.setmapvert obj channelindex vertindex average
|
|
)
|
|
)
|
|
)
|
|
catch
|
|
(
|
|
gRsUlog.LogError ("Cloth colour baking failed for object "+obj.name+". Please remove invalid modifiers. \nError:"+(getCurrentException())) context:obj
|
|
)
|
|
update obj
|
|
)
|
|
),
|
|
|
|
fn UnifyNormals objs =
|
|
(
|
|
print ("UnifyNormals "+objs as string)
|
|
if (classof objs) != Array then
|
|
(
|
|
objs = #(objs)
|
|
)
|
|
|
|
for obj in objs do
|
|
(
|
|
themesh = Editable_Mesh()
|
|
themesh.mesh = copy obj.baseobject.mesh
|
|
local baseObjClass = classof obj.baseobject
|
|
select themesh
|
|
addmodifier themesh (Edit_Normals())
|
|
numNormals =themesh.Edit_Normals.GetNumNormals()
|
|
my_normals = (for vi = 1 to numNormals collect vi) as bitarray
|
|
themesh.Edit_Normals.unify node:themesh selection:(my_normals)
|
|
convertto themesh baseObjClass
|
|
obj.baseobject = themesh
|
|
delete themesh
|
|
select obj
|
|
update obj
|
|
)
|
|
),
|
|
|
|
fn GetRenderModelName obj =
|
|
(
|
|
local renderModelName = ""
|
|
-- setup seelction set and user props
|
|
if RsLodDrawable_HasRenderModel obj then
|
|
(
|
|
local renderModel = (RsLodDrawable_GetRenderModel obj)
|
|
append renderModelName renderModel.name
|
|
)
|
|
return renderModelName
|
|
),
|
|
|
|
fn SetupClothExpectedVertCount theMesh meshes:undefined =
|
|
(
|
|
local paramString = ""
|
|
local insertSeparator = false
|
|
for m in meshes do
|
|
(
|
|
if insertSeparator then
|
|
paramString = paramString + "|"
|
|
else
|
|
insertSeparator = true
|
|
paramString = paramString + m.name + ":" + (getnumverts m) as string
|
|
)
|
|
print ("paramString:"+paramString)
|
|
rexSetPropertyValue theMesh "ExpectedVertexCount" paramString
|
|
rexSetPropertyValue theMesh "CullVertexChannels" "2" -- culling from 1 to <number of channels on object>
|
|
),
|
|
|
|
fn SetupClothMeshesForExport meshes =
|
|
(
|
|
bakeVertexChannels meshes
|
|
UnifyNormals meshes
|
|
),
|
|
|
|
fn BuildChannelMapStringWithVariations objs =
|
|
(
|
|
local blindChannels = channelMap
|
|
blindChannels = substring blindChannels 1 (blindChannels.count-1)
|
|
for entry in RAGEClothChannelTable do
|
|
(
|
|
local variationChannel = entry.vDataVariationOffset
|
|
if undefined!=variationChannel then
|
|
(
|
|
for channeloffset = 0 to gVariationChannelsPerChannel do
|
|
(
|
|
local channelindex = variationChannel + channeloffset
|
|
local useChannel = false
|
|
for obj in objs do
|
|
(
|
|
-- print ("test "+obj as string+" with channel "+channelindex as string)
|
|
if GetChannelSupport obj channelindex then
|
|
useChannel = true
|
|
)
|
|
if useChannel then
|
|
(
|
|
local entryName = ((RsFileSafeString entry.userName)+"_Variation"+(channeloffset+1) as string)
|
|
append blindChannels (";"+entryName+"|"+channelindex as string+"|"+channelindex as string+"|"+entry.defaultVal as string)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
append blindChannels ")"
|
|
print blindChannels
|
|
blindChannels
|
|
),
|
|
|
|
----------------------------------------------------------------------------------------------
|
|
-- Environment cloth facuntions
|
|
----------------------------------------------------------------------------------------------
|
|
--
|
|
-- name: SetupEnvClothForExport
|
|
-- desc: Setup environment cloth for export.
|
|
--
|
|
fn SetupEnvClothForExport objs rexentity rexmesh fragroot: = (
|
|
|
|
-- Add the EnvCloth child
|
|
local envCloth = rexAddChild rexentity "EnvCloth" "EnvCloth"
|
|
local envClothPiece = rexAddChild envCloth "ClothPiece" "ClothPiece"
|
|
|
|
-- Setup mesh (default setting for now)
|
|
local blindChannels = BuildChannelMapStringWithVariations objs
|
|
rexSetPropertyValue rexmesh "MeshBlindVertexDataChannels" blindChannels
|
|
|
|
local savesel = selection as array
|
|
|
|
local objToOptimise = #() + objs
|
|
local objsToExport = #()
|
|
for obj in objs where obj!=undefined do
|
|
(
|
|
local renderModel = (RsLodDrawable_GetRenderModel obj)
|
|
if undefined!=renderModel then
|
|
append objToOptimise renderModel
|
|
|
|
local simModel = (RsLodDrawable_GetSimulationModel obj)
|
|
local isHighestLODModel = not (RsLodDrawable_HasHigherDetailModel obj)
|
|
if undefined==simModel and isHighestLODModel then
|
|
append objsToExport obj
|
|
|
|
if unsupplied!=fragroot then
|
|
(
|
|
local offset = obj.pos - fragroot.pos
|
|
setuserprop obj "fragmentOffset" (offset.x as string+","+offset.y as string+","+offset.z as string)
|
|
)
|
|
)
|
|
SetupClothMeshesForExport objToOptimise
|
|
|
|
select objsToExport
|
|
gRsUlog.LogMessage ("env CLOTHPIECE node selection: "+objsToExport as string)
|
|
rexSetNodes envClothPiece
|
|
select savesel
|
|
|
|
true
|
|
),
|
|
|
|
----------------------------------------------------------------------------------------------
|
|
-- Character cloth facuntions
|
|
----------------------------------------------------------------------------------------------
|
|
fn SetupCharClothNodeSelection obj =
|
|
(
|
|
clearSelection()
|
|
|
|
if ( IsCloth obj ) then
|
|
(
|
|
-- Attach bounds (optional)
|
|
for child in obj.children do
|
|
(
|
|
-- Ignore non-collision primitives
|
|
if ( "Gta Collision" == ( GetAttrClass child ) ) then
|
|
selectMore child
|
|
)
|
|
)
|
|
else
|
|
(
|
|
-- ped parts dont use lod groups
|
|
RsUserProp_Clear obj "optimseMeshForCloth"
|
|
RsUserProp_Clear obj "lodgroup"
|
|
RsUserProp_Clear obj "optimize"
|
|
)
|
|
|
|
-- setup seelction set and user props
|
|
setuserprop obj "lodgroup" 0
|
|
if RsLodDrawable_HasRenderModel obj then
|
|
(
|
|
local renderModel = (RsLodDrawable_GetRenderModel obj)
|
|
local charclothsectionname = renderModel.name
|
|
setuserprop renderModel "lodgroup" 0
|
|
setuserprop renderModel "optimize" false
|
|
setUserProp renderModel "exclusiveTypeGroups" ("lodgroup|fragments|"+charclothsectionname)
|
|
RsUserProp_Clear renderModel "optimseMeshForCloth"
|
|
selectMore renderModel
|
|
selectMore obj
|
|
setuserprop obj "lodgroup" 0
|
|
setUserProp obj "exclusiveTypeGroups" charclothsectionname
|
|
setuserprop obj "optimseMeshForCloth" true
|
|
)
|
|
else if RsLodDrawable_HasSimulationModel obj then
|
|
(
|
|
local simModel = (RsLodDrawable_GetSimulationModel obj)
|
|
local charclothsectionname = obj.name
|
|
setUserProp obj "exclusiveTypeGroups" ("lodgroup|fragments|"+charclothsectionname)
|
|
setuserprop obj "lodgroup" 0
|
|
selectMore obj
|
|
RsUserProp_Clear obj "optimseMeshForCloth"
|
|
selectMore simModel
|
|
setuserprop simModel "optimseMeshForCloth" true
|
|
setuserprop simModel "lodgroup" 0
|
|
setuserprop simModel "optimize" false
|
|
setUserProp simModel "exclusiveTypeGroups" charclothsectionname
|
|
)
|
|
else
|
|
return false
|
|
|
|
return true
|
|
),
|
|
|
|
----------------------------------------------------------------------------------------------
|
|
-- Alternative to above without use of select bugstar:1880733
|
|
----------------------------------------------------------------------------------------------
|
|
fn SetupCharClothNodeArray obj NodeArray =
|
|
(
|
|
if ( IsCloth obj ) then
|
|
(
|
|
-- Attach bounds (optional)
|
|
for child in obj.children do
|
|
(
|
|
-- Ignore non-collision primitives
|
|
if ( "Gta Collision" == ( GetAttrClass child ) ) then
|
|
append NodeArray child
|
|
)
|
|
)
|
|
else
|
|
(
|
|
-- ped parts dont use lod groups
|
|
RsUserProp_Clear obj "optimseMeshForCloth"
|
|
RsUserProp_Clear obj "lodgroup"
|
|
RsUserProp_Clear obj "optimize"
|
|
)
|
|
|
|
-- setup seelction set and user props
|
|
setuserprop obj "lodgroup" 0
|
|
if RsLodDrawable_HasRenderModel obj then
|
|
(
|
|
local renderModel = (RsLodDrawable_GetRenderModel obj)
|
|
local charclothsectionname = renderModel.name
|
|
setuserprop renderModel "lodgroup" 0
|
|
setuserprop renderModel "optimize" false
|
|
setUserProp renderModel "exclusiveTypeGroups" ("lodgroup|fragments|"+charclothsectionname)
|
|
RsUserProp_Clear renderModel "optimseMeshForCloth"
|
|
append NodeArray renderModel
|
|
append NodeArray obj
|
|
setuserprop obj "lodgroup" 0
|
|
setUserProp obj "exclusiveTypeGroups" charclothsectionname
|
|
setuserprop obj "optimseMeshForCloth" true
|
|
)
|
|
else if RsLodDrawable_HasSimulationModel obj then
|
|
(
|
|
local simModel = (RsLodDrawable_GetSimulationModel obj)
|
|
local charclothsectionname = obj.name
|
|
setUserProp obj "exclusiveTypeGroups" ("lodgroup|fragments|"+charclothsectionname)
|
|
setuserprop obj "lodgroup" 0
|
|
append NodeArray obj
|
|
RsUserProp_Clear obj "optimseMeshForCloth"
|
|
append NodeArray simModel
|
|
setuserprop simModel "optimseMeshForCloth" true
|
|
setuserprop simModel "lodgroup" 0
|
|
setuserprop simModel "optimize" false
|
|
setUserProp simModel "exclusiveTypeGroups" charclothsectionname
|
|
)
|
|
else
|
|
return false
|
|
|
|
return true
|
|
),
|
|
|
|
--
|
|
-- name: SetupCharClothForExport
|
|
-- desc: Setup character cloth for export.
|
|
--
|
|
fn SetupCharClothForExport obj skelroot outputdir rexentity rexmesh = (
|
|
|
|
-- Add the EnvCloth child
|
|
local envCloth = rexAddChild rexentity "CharCloth" "CharCloth"
|
|
local renderModelName = GetRenderModelName obj
|
|
local envClothPiece = rexAddChild envCloth (if renderModelName!="" then renderModelName else obj.name) "ClothPiece"
|
|
|
|
-- Setup mesh (default setting for now)
|
|
local blindChannels = BuildChannelMapStringWithVariations #(obj)
|
|
rexSetPropertyValue rexmesh "MeshBlindVertexDataChannels" blindChannels
|
|
|
|
-- set selection to rex input for CLOTH definiition
|
|
gRsULog.LogMessage ("CLOTH SELECTION: "+((selection as array) as string))
|
|
for m in (selection as array) do
|
|
gRsULog.LogMessage ("lod group of "+m.name+": "+(getuserprop m "lodgroup") as string)
|
|
rexSetNodes envClothPiece
|
|
|
|
true
|
|
),
|
|
|
|
--
|
|
-- name: SetupCharClothForExportWithoutSelect
|
|
-- desc: Setup character cloth for export, don't use the current maxScript selection (which can be slow)
|
|
--
|
|
fn SetupCharClothForExportWithoutSelect obj skelroot outputdir rexentity rexmesh nodes = (
|
|
|
|
-- Add the EnvCloth child
|
|
local envCloth = rexAddChild rexentity "CharCloth" "CharCloth"
|
|
local renderModelName = GetRenderModelName obj
|
|
local envClothPiece = rexAddChild envCloth (if renderModelName!="" then renderModelName else obj.name) "ClothPiece"
|
|
|
|
-- Setup mesh (default setting for now)
|
|
local blindChannels = BuildChannelMapStringWithVariations #(obj)
|
|
rexSetPropertyValue rexmesh "MeshBlindVertexDataChannels" blindChannels
|
|
|
|
-- set selection to rex input for CLOTH definiition
|
|
gRsULog.LogMessage ("CLOTH SELECTION: "+(nodes as string))
|
|
for m in nodes do
|
|
gRsULog.LogMessage ("lod group of "+m.name+": "+(getuserprop m "lodgroup") as string)
|
|
rexSetNodes envClothPiece exportnodes:(RsGetAnimHandleString nodes)
|
|
|
|
true
|
|
),
|
|
|
|
--
|
|
-- Export Cloth collision xml file
|
|
-- this is to be replaced with an asset build step post GTAV
|
|
--
|
|
fn ExportClothCollisionXML clothobjs assetPath =
|
|
(
|
|
local idxClothindex = getAttrindex "Gta Object" "Is Cloth"
|
|
local allClothObjs = #()
|
|
|
|
local clothCollXml = XmlDocument()
|
|
clothCollXml.Init()
|
|
|
|
verletCloth = clothCollXml.createelement("rage__phVerletClothCustomBounds")
|
|
clothCollXml.document.AppendChild verletCloth
|
|
|
|
collisionXmlRoot = clothCollXml.createelement("CollisionData") appendTo:verletCloth
|
|
|
|
for obj in clothobjs do
|
|
(
|
|
local collKids = #()
|
|
RsSceneLink.getChildren LinkType_ClothCollision obj &collKids
|
|
local inverseItemTransform = inverse obj.transform
|
|
|
|
for col in collKids where
|
|
Col_Capsule == classof col or
|
|
Col_Plane == classof col
|
|
do
|
|
(
|
|
local item = clothCollXml.createelement("Item") appendTo:collisionXmlRoot
|
|
local localTrans = col.transform * inverseItemTransform
|
|
clothCollXml.createelement "OwnerName" appendTo:item value:obj.name
|
|
clothCollXml.createelement "Rotation" appendTo:item attrs:#(
|
|
#("x", localTrans.rotation.x),
|
|
#("y", localTrans.rotation.y),
|
|
#("z", localTrans.rotation.z),
|
|
#("w", localTrans.rotation.w)
|
|
)
|
|
clothCollXml.createelement "Position" appendTo:item attrs:#(
|
|
#("x", localTrans.pos.x),
|
|
#("y", localTrans.pos.y),
|
|
#("z", localTrans.pos.z)
|
|
)
|
|
local normal = (Point3 0 0 0)
|
|
if Col_Plane == classof col then
|
|
(
|
|
case col.Orientation of
|
|
(
|
|
0: normal = (Point3 0 0 1)
|
|
1: normal = (Point3 0 1 0)
|
|
2: normal = (Point3 1 0 0)
|
|
)
|
|
if isProperty col "rotation" then
|
|
normal = normal * col.rotation
|
|
)
|
|
clothCollXml.createelement "Normal" appendTo:item attrs:#(
|
|
#("x", normal.x),
|
|
#("y", normal.y),
|
|
#("z", normal.z)
|
|
)
|
|
clothCollXml.createelement "CapsuleRadius" appendTo:item attrs:#(#("value", col.radius))
|
|
clothCollXml.createelement "CapsuleLen" appendTo:item attrs:#(#("value", col.length))
|
|
clothCollXml.createelement "CapsuleHalfHeight" appendTo:item attrs:#(#("value", 0.0f))
|
|
clothCollXml.createelement "CapsuleHalfWidth" appendTo:item attrs:#(#("value", 0.0f))
|
|
)
|
|
)
|
|
|
|
local outputPath = assetPath+"/clothCollision.xml"
|
|
clothCollXml.save outputPath
|
|
)
|
|
)
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Struct Global Instances
|
|
-----------------------------------------------------------------------------
|
|
global RsCloth = RsClothStruct()
|
|
|
|
|
|
-- cloth.ms
|