656 lines
18 KiB
Plaintext
Executable File
656 lines
18 KiB
Plaintext
Executable File
--ShaderAnalysisTool.ms
|
|
--2013 Andy Davis
|
|
--Rockstar London
|
|
--Script displays information about multi-layered shaders
|
|
|
|
FileIn (RsConfigGetWildWestDir() + "script/3dsMax/_config_files/Wildwest_header.ms")
|
|
|
|
--UI Xaml Markup
|
|
UIMarkupFilePath = @"X:\gta5\tools\wildwest\script\3dsMax\UI\ShaderAnalysisTool.xaml"
|
|
|
|
--Custom dotNet Class
|
|
CSharp.CompileToMemory #(::RsConfigGetWildwestDir() + @"script\3dsMax\Maps\Materials\ShaderAnalysisTool.cs")
|
|
|
|
global ShaderAnalysisToolUI
|
|
global ToolFunctions
|
|
|
|
struct MaterialInfo
|
|
(
|
|
mat,
|
|
faceList,
|
|
shaderClass,
|
|
rageType,
|
|
isLayered = false,
|
|
diffuseMapList = #()
|
|
)
|
|
|
|
--------------------------------------------------------------------------------------------------------------------------------
|
|
--functions that are candidates to be made generic
|
|
--------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--function returns the shader layer Influences when passed the green and blue components of a map value
|
|
--InMapValue: send the value for map channel 9 to the function
|
|
--Returns: 4 part array with the influences of each of the four shaders
|
|
fn RSMaterial_Get2LayerShaderInfluences InMapValue =
|
|
(
|
|
local G = InMapValue[2] --green component of map value
|
|
local B = InMapValue[3] --blue component of map value
|
|
local Influences = #()
|
|
|
|
if (G > B) then
|
|
(
|
|
Influences[1] = 1 - G
|
|
Influences[4] = B
|
|
)
|
|
else
|
|
(
|
|
Influences[1] = 1 - B
|
|
Influences[4] = G
|
|
)
|
|
|
|
if (1 - G > B) then
|
|
(
|
|
Influences[2] = B
|
|
Influences[3] = G
|
|
)
|
|
else
|
|
(
|
|
Influences[2] = 1 - G
|
|
Influences[3] = 1 - B
|
|
)
|
|
|
|
return Influences
|
|
)
|
|
|
|
--returns a material from a multi-material when passed the id or undefined if nout found
|
|
fn RSMaterials_GetMaterialFromMulti inMulti inMatID =
|
|
(
|
|
local outMaterial = undefined
|
|
local notFound = true
|
|
|
|
for i = 1 to inMulti.MaterialList.count while notFound do
|
|
(
|
|
if inMulti.MaterialIDList[i] == inMatID then
|
|
(
|
|
outMaterial = inMulti.MaterialList[i]
|
|
notFound = false
|
|
)
|
|
)
|
|
|
|
return outMaterial
|
|
)
|
|
|
|
--returns an array of MaterialInfo objects
|
|
fn RSMaterials_GetMaterials InObject =
|
|
(
|
|
local MaterialInfoList = #()
|
|
local materialList = #()
|
|
local mat = InObject.material
|
|
local modAdded = false
|
|
|
|
if (classOf InObject != editable_mesh) then
|
|
(
|
|
addModifier InObject (Edit_Mesh())
|
|
modAdded = true
|
|
)
|
|
|
|
if (classOf InObject.material == MultiMaterial) then
|
|
(
|
|
--get a list of each unique material id used on the object, and corresponding faces
|
|
local faceCount = InObject.faces.count
|
|
local matIDList = #() --list of unique face ids
|
|
local matIDFaceList = #() --corresponding faces
|
|
|
|
for face = 1 to faceCount do
|
|
(
|
|
local matID = getFaceMatID InObject face
|
|
local item = findItem matIDList matID
|
|
|
|
if (item == 0) then
|
|
(
|
|
append matIDList matID
|
|
append matIDFaceList #{face}
|
|
)
|
|
else
|
|
(
|
|
append matIDFaceList[item] face
|
|
)
|
|
)
|
|
|
|
--work out the material corresponding to id's in the material id list
|
|
for i = 1 to matIDList.count do
|
|
(
|
|
local thisMat = RSMaterials_GetMaterialFromMulti InObject.material matIDList[i]
|
|
append materialList (dataPair material:thisMat faces:matIDFaceList[i])
|
|
)
|
|
)
|
|
else
|
|
(
|
|
append materialList (dataPair material:InObject.Material faces:#{1..InObject.faces.count})
|
|
)
|
|
|
|
-- clearListener()
|
|
|
|
for item in materialList do
|
|
(
|
|
local shaderType = undefined
|
|
local diffuseMaps = #()
|
|
local isLayered = false
|
|
|
|
if (classOf item.material == Rage_Shader) then
|
|
(
|
|
shaderType = RstGetShaderName item.material
|
|
local variableCount = RstGetVariableCount item.material
|
|
|
|
if (findString shaderType "terrain_cb_") != undefined then
|
|
(
|
|
isLayered = true
|
|
|
|
local firstMap = 0
|
|
|
|
for i = 1 to variableCount while firstMap == 0 do
|
|
(
|
|
if (RstGetVariableType item.material i) == "texmap" then
|
|
(
|
|
--check this is a diffuse map
|
|
if ((findString (RstGetVariableName item.material i) "Diffuse" != undefined)) then
|
|
(
|
|
firstMap = i
|
|
)
|
|
)
|
|
)
|
|
|
|
local thisIsNormalMap = false
|
|
--maps alternate between diffuse and normal, use this logic operation to get alternate maps
|
|
|
|
for i = firstMap to (firstMap + 7) do
|
|
(
|
|
if (thisIsNormalMap = not thisIsNormalMap) then
|
|
(
|
|
append diffuseMaps (RstGetVariable item.material i)
|
|
)
|
|
)
|
|
)
|
|
else
|
|
(
|
|
local mapNotFound = true
|
|
|
|
for i = 1 to variableCount while mapNotFound do
|
|
(
|
|
if (RstGetVariableType item.material i) == "texmap" then
|
|
(
|
|
mapNotFound = false
|
|
append diffuseMaps (RstGetVariable item.material i)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
local newMaterialInfo = MaterialInfo()
|
|
newMaterialInfo.mat = item.material
|
|
newMaterialInfo.faceList = item.faces
|
|
newMaterialInfo.shaderClass = (classOf item.material)
|
|
newMaterialInfo.rageType = shaderType
|
|
newMaterialInfo.diffuseMapList = diffuseMaps
|
|
newMaterialInfo.isLayered = isLayered
|
|
append MaterialInfoList newMaterialInfo
|
|
)
|
|
|
|
if (modAdded) then deleteModifier InObject InObject.Edit_Mesh
|
|
|
|
return MaterialInfoList
|
|
)
|
|
|
|
--------------------------------------------------------------------------------------------------------------------------------
|
|
--tool functionality
|
|
--------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
struct ToolFunctionsStruct
|
|
(
|
|
ShaderList = #(),
|
|
AnalyseButton,
|
|
ShaderAnalysisTool,
|
|
ObjectList = #(),
|
|
|
|
fn PrintMaterialInfoList InShaderList =
|
|
(
|
|
for item in InShaderList do
|
|
(
|
|
format "Material: %\n" item.mat.name
|
|
format "Face list: %\n" item.faceList
|
|
format "Shader class: %\n" item.shaderClass
|
|
format "Rage type: %\n" item.rageType
|
|
format "Layered shader? %\n" item.isLayered
|
|
for map in item.diffuseMapList do format "diffuse map: %\n" map
|
|
format "\n"
|
|
)
|
|
),
|
|
|
|
|
|
fn GetShaderLayerInfo InObject InMaterialInfo =
|
|
(
|
|
local ShaderLayerInfo = #(0, 0, 0, 0)
|
|
local faceList = InMaterialInfo.FaceList
|
|
|
|
--get the total influences of each of the four materials in the layered shader by getting the map channel data on each subvert
|
|
|
|
for face in faceList do
|
|
(
|
|
local vertList = (meshop.GetVertsUsingFace InObject face) as array
|
|
|
|
for vert in vertList do
|
|
(
|
|
local shaderLayerMapChannel = 9
|
|
local mapValue = RSMesh_GetSubVert_MapValue InObject vert face shaderLayerMapChannel
|
|
for i = 1 to 3 do this.ClampValueZeroOne &mapValue[i] --adjust values not in the zero to one value range
|
|
local influence = RSMaterial_Get2LayerShaderInfluences mapValue
|
|
for i = 1 to 4 do ShaderLayerInfo[i] += influence[i]
|
|
)
|
|
)
|
|
|
|
--convert the influences into percentage ratios by dividing by the total and multiplying by 100
|
|
|
|
local divisor = ShaderLayerInfo[1] + ShaderLayerInfo[2] + ShaderLayerInfo[3] + ShaderLayerInfo[4]
|
|
|
|
for i = 1 to 4 do ShaderLayerInfo[i] *= 100.0 / divisor
|
|
|
|
return ShaderLayerInfo
|
|
),
|
|
|
|
fn ClampValueZeroOne &InValue =
|
|
(
|
|
if (InValue > 1) then
|
|
InValue = InValue - (floor InValue)
|
|
else if (InValue < 0) then
|
|
InValue = (ceil InValue) - InValue
|
|
),
|
|
|
|
fn GetObjectData =
|
|
(
|
|
ToolFunctions.ObjectList.ObjectDataList.Clear()
|
|
|
|
local ProgressValue = 0
|
|
|
|
objList = for item in selection where SuperClassOf item == GeometryClass collect item
|
|
|
|
--Create our top progress window
|
|
ProgressWindow = RSProgressWindow Title:"ProgressWindow" StartStep:0 EndStep:objList.count
|
|
ProgressWindow.Start()
|
|
|
|
|
|
for obj in objList do
|
|
(
|
|
AddModifier obj (Edit_Mesh())
|
|
local newObjDataItem = dotNetObject "ShaderAnalysis.ObjectData"
|
|
newObjDataItem.Handle = obj.Handle
|
|
newObjDataItem.Name = obj.Name
|
|
newObjDataItem.FaceCount = obj.faces.count
|
|
local ShaderList = RSMaterials_GetMaterials obj
|
|
|
|
for thisShader in ShaderList do
|
|
(
|
|
local newDesc = dotNetObject "ShaderAnalysis.MaterialDescription"
|
|
|
|
newDesc.ObjectHandle = obj.handle
|
|
|
|
if thisShader.mat == undefined then
|
|
(
|
|
newDesc.Name = "no material"
|
|
newDesc.Handle = -1
|
|
newDesc.TextureMapList = undefined
|
|
newDesc.ShaderType = "\t"
|
|
)
|
|
else
|
|
(
|
|
newDesc.Name = thisShader.mat.name
|
|
newDesc.Handle = GetHandleByAnim thisShader.mat
|
|
newDesc.ShaderType = thisShader.RageType
|
|
newDesc.Tint = (dotNetClass "System.Windows.Media.Color").FromRGB (random 80 255) (random 80 255) (random 80 255)
|
|
|
|
local TextureMapList = #()
|
|
|
|
for map in thisShader.diffuseMapList where (isKindOf map String) do
|
|
(
|
|
local newMap = dotNetObject "ShaderAnalysis.TextureMap"
|
|
newMap.Path = map
|
|
append TextureMapList newMap
|
|
)
|
|
|
|
newDesc.TextureMapList = TextureMapList
|
|
|
|
if thisShader.isLayered then
|
|
(
|
|
local shaderLayerPercentages = this.GetShaderLayerInfo obj thisShader
|
|
|
|
for i = 1 to 4 do
|
|
newDesc.TextureMapList[i].Usage = shaderLayerPercentages[i]
|
|
)
|
|
else
|
|
(
|
|
newDesc.TextureMapList[1].Usage = 100
|
|
)
|
|
)
|
|
|
|
newDesc.FaceList = (thisShader.faceList) as array
|
|
newObjDataItem.CalculatePercentage newDesc
|
|
newObjDataItem.ShaderList.Add newDesc
|
|
)
|
|
|
|
ToolFunctions.ObjectList.ObjectDataList.Add newObjDataItem
|
|
DeleteModifier obj obj.Edit_Mesh
|
|
ProgressWindow.PostProgressStep()
|
|
)
|
|
|
|
--End progress
|
|
ProgressWindow.End()
|
|
),
|
|
|
|
fn AnalyseButtonPressed =
|
|
(
|
|
ToolFunctions.DeleteShaderProxy()
|
|
ToolFunctions.GetObjectData()
|
|
),
|
|
|
|
fn InitStuff =
|
|
(
|
|
--analyse button
|
|
this.AnalyseButton = ShaderAnalysisToolUI.Content.FindName("AnalyseSelectedButton")
|
|
dotnet.AddEventHandler AnalyseButton "click" AnalyseButtonPressed
|
|
|
|
--select and zoom button
|
|
this.ShaderAnalysisTool = dotNetObject "ShaderAnalysis.ShaderAnalysisTool"
|
|
ShaderAnalysisTool.BindElement ShaderAnalysisToolUI.content
|
|
|
|
--ObjectList and bind
|
|
ToolFunctions.ObjectList = dotNetObject "ShaderAnalysis.BindingList"
|
|
ToolFunctions.GetObjectData()
|
|
ShaderAnalysisToolUI.Content.DataContext = ObjectList.ObjectDataList
|
|
),
|
|
|
|
--returns an array of MaterialInfo objects
|
|
fn GetMaterials InObject =
|
|
(
|
|
local MaterialInfoList = #()
|
|
local materialList = #()
|
|
local mat = InObject.material
|
|
local modAdded = false
|
|
|
|
if (classOf InObject != editable_mesh) then
|
|
(
|
|
addModifier InObject (Edit_Mesh())
|
|
modAdded = true
|
|
)
|
|
|
|
if (classOf InObject.material == MultiMaterial) then
|
|
(
|
|
--get a list of each unique material id used on the object, and corresponding faces
|
|
local faceCount = InObject.faces.count
|
|
local matIDList = #() --list of unique face ids
|
|
local matIDFaceList = #() --corresponding faces
|
|
|
|
for face = 1 to faceCount do
|
|
(
|
|
local matID = getFaceMatID InObject face
|
|
local item = findItem matIDList matID
|
|
|
|
if (item == 0) then
|
|
(
|
|
append matIDList matID
|
|
append matIDFaceList #{face}
|
|
)
|
|
else
|
|
(
|
|
append matIDFaceList[item] face
|
|
)
|
|
)
|
|
|
|
--work out the material corresponding to id's in the material id list
|
|
for i = 1 to matIDList.count do
|
|
(
|
|
local thisMat = RSMaterials_GetMaterialFromMulti InObject.material matIDList[i]
|
|
append materialList (dataPair material:thisMat faces:matIDFaceList[i])
|
|
)
|
|
)
|
|
else
|
|
(
|
|
append materialList (dataPair material:InObject.Material faces:#{1..InObject.faces.count})
|
|
)
|
|
|
|
-- clearListener()
|
|
|
|
for item in materialList do
|
|
(
|
|
local shaderType = undefined
|
|
local diffuseMaps = #()
|
|
local isLayered = false
|
|
local newMaterialInfo = MaterialInfo()
|
|
|
|
if (classOf item.material == Rage_Shader) then
|
|
(
|
|
shaderType = RstGetShaderName item.material
|
|
local variableCount = RstGetVariableCount item.material
|
|
|
|
if (findString shaderType "terrain_cb_") != undefined then
|
|
(
|
|
isLayered = true
|
|
|
|
local firstMap = 0
|
|
|
|
for i = 1 to variableCount while firstMap == 0 do
|
|
(
|
|
if (RstGetVariableType item.material i) == "texmap" then
|
|
(
|
|
firstMap = i
|
|
)
|
|
)
|
|
|
|
local thisIsNormalMap = false
|
|
--maps alternate between diffuse and normal, use this logic operation to get alternate maps
|
|
|
|
for i = firstMap to (firstMap + 7) do
|
|
(
|
|
if (thisIsNormalMap = not thisIsNormalMap) then
|
|
(
|
|
append diffuseMaps (RstGetVariable item.material i)
|
|
)
|
|
)
|
|
)
|
|
else
|
|
(
|
|
local mapNotFound = true
|
|
|
|
for i = 1 to variableCount while mapNotFound do
|
|
(
|
|
if (RstGetVariableType item.material i) == "texmap" then
|
|
(
|
|
mapNotFound = false
|
|
append diffuseMaps (RstGetVariable item.material i)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
newMaterialInfo.mat = item.material
|
|
newMaterialInfo.faceList = item.faces
|
|
newMaterialInfo.shaderClass = (classOf item.material)
|
|
newMaterialInfo.rageType = shaderType
|
|
newMaterialInfo.diffuseMapList = diffuseMaps
|
|
newMaterialInfo.isLayered = isLayered
|
|
append MaterialInfoList newMaterialInfo
|
|
)
|
|
|
|
if (modAdded) then deleteModifier InObject InObject.Edit_Mesh
|
|
|
|
return MaterialInfoList
|
|
),
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
--function creates a tinted proxy object representing the shaders in the target object
|
|
------------------------------------------------------------------------------------------------------
|
|
fn CreateShaderProxy InObject =
|
|
(
|
|
local nodeArray = undefined
|
|
local NewMaterialList = #()
|
|
max modify mode
|
|
local SubObjectMode = SubObjectLevel
|
|
|
|
local proxyObjects = for item in objects where (item.name == "ShaderAnalysis_Proxy") collect item
|
|
|
|
if not this.DeleteShaderProxy() then
|
|
(
|
|
if (classOf InObject.material == MultiMaterial) then
|
|
(
|
|
maxops.cloneNodes InObject cloneType:#reference newNodes:&nodeArray
|
|
local ShaderProxy = nodeArray[1]
|
|
ShaderProxy.name = "ShaderAnalysis_Proxy"
|
|
ShaderProxy.showFrozenInGray = off
|
|
ShaderProxy.showVertexColors = off
|
|
AddModifier ShaderProxy (Push Push_Value:0.1)
|
|
freeze ShaderProxy
|
|
setAttr ShaderProxy (getAttrIndex "Gta Object" "Dont Export") true
|
|
setAttr ShaderProxy (getAttrIndex "Gta Object" "Dont Add To IPL") true
|
|
|
|
--find the object in the ObjectDataList
|
|
--get the material description list
|
|
local numObjs = ToolFunctions.ObjectList.ObjectDataList.Count
|
|
local MaterialData = undefined
|
|
|
|
for i = 0 to (numObjs - 1) do
|
|
(
|
|
if (InObject.Handle == ToolFunctions.ObjectList.ObjectDataList.Item[i].Handle) then
|
|
(
|
|
MaterialData = ToolFunctions.ObjectList.ObjectDataList.Item[i].ShaderList
|
|
)
|
|
)
|
|
|
|
--for each material description, get the tint value and the material name
|
|
if (MaterialData != undefined) then
|
|
(
|
|
local MaterialIDList = #()
|
|
local numMaterials = MaterialData.count
|
|
NewMulti = MultiMaterial name:"ShaderProxyMaterial"
|
|
meditMaterials[activeMeditSlot] = NewMulti
|
|
|
|
for i = 0 to (numMaterials - 1) do
|
|
(
|
|
local shaderNode = MaterialData.Item[i]
|
|
local NewColor = color (shaderNode.Tint.R) (shaderNode.Tint.G) (shaderNode.Tint.B)
|
|
local currentMaterial = GetAnimByHandle shaderNode.Handle
|
|
local NewMaterial = StandardMaterial name:currentMaterial.name diffuse:NewColor opacity:50
|
|
append MaterialIDList (RSMaterials_GetMaterialIDFromMulti InObject.material currentMaterial)
|
|
append NewMaterialList NewMaterial
|
|
)
|
|
|
|
NewMulti.MaterialList = NewMaterialList
|
|
NewMulti.MaterialIDList = MaterialIDList
|
|
ShaderProxy.Material = NewMulti
|
|
ShaderProxy
|
|
)
|
|
else
|
|
(
|
|
messagebox "Object not found" title:"Shader Analysis Tool"
|
|
)
|
|
)
|
|
else
|
|
(
|
|
messagebox "Single material on object" title:"Shader Analysis Tool"
|
|
)
|
|
)
|
|
|
|
SubObjectLevel = SubObjectMode
|
|
),
|
|
|
|
fn DeleteShaderProxy =
|
|
(
|
|
local proxyObjects = for item in objects where (item.name == "ShaderAnalysis_Proxy") collect item
|
|
if proxyObjects.count > 0 then
|
|
(
|
|
delete proxyObjects
|
|
return true
|
|
)
|
|
else return false
|
|
)
|
|
)
|
|
|
|
fn SelectAndZoomAction s e =
|
|
(
|
|
local obj = MaxOps.getNodeByHandle e.Data.ObjectHandle
|
|
local faceList = e.Data.FaceList as bitarray
|
|
|
|
if obj == undefined then
|
|
(
|
|
messagebox "Object no longer in scene" title:"Shader Analysis Tool"
|
|
)
|
|
else
|
|
(
|
|
select obj
|
|
|
|
if (obj.modifiers.count > 0) then
|
|
(
|
|
if (classOf obj.modifiers[1] != Edit_Mesh) then
|
|
(
|
|
AddModifier obj (Edit_Mesh())
|
|
)
|
|
)
|
|
else if classOf obj == Editable_Poly then
|
|
(
|
|
convertToMesh obj
|
|
)
|
|
|
|
|
|
if (classOf obj != Editable_Mesh) then
|
|
AddModifier obj (Edit_Mesh())
|
|
|
|
max modify mode
|
|
SubObjectLevel = 4
|
|
|
|
if (classOf obj == Editable_Mesh) then
|
|
(
|
|
SetFaceSelection obj faceList
|
|
)
|
|
else
|
|
(
|
|
PolyOp.SetFaceSelection obj faceList
|
|
)
|
|
|
|
max zoomext sel
|
|
)
|
|
)
|
|
|
|
fn TintShadersAction s e =
|
|
(
|
|
local obj = MaxOps.getNodeByHandle e.Data.Handle
|
|
|
|
if obj == undefined then
|
|
(
|
|
messagebox "Object no longer in scene" title:"Shader Analysis Tool"
|
|
)
|
|
else
|
|
(
|
|
if obj.material != undefined then
|
|
(
|
|
ToolFunctions.CreateShaderProxy obj
|
|
)
|
|
else
|
|
(
|
|
messagebox "No shaders found to tint" title:"Shader Analysis Tool"
|
|
)
|
|
)
|
|
)
|
|
|
|
fn OnCloseAction s e =
|
|
(
|
|
ToolFunctions.DeleteShaderProxy()
|
|
)
|
|
|
|
ToolFunctions = ToolFunctionsStruct()
|
|
|
|
--Setup our tool window
|
|
if ShaderAnalysisToolUI != undefined then ShaderAnalysisToolUI.Form.Close()
|
|
|
|
ShaderAnalysisToolUI = RSToolWindow Name:"Shader Analysis Tool" UISource:UIMarkupFilePath
|
|
ToolFunctions.InitStuff()
|
|
dotnet.AddEventHandler ToolFunctions.ShaderAnalysisTool "SelectAndZoom" SelectAndZoomAction
|
|
dotnet.AddEventHandler ToolFunctions.ShaderAnalysisTool "TintShaders" TintShadersAction
|
|
dotnet.AddEventHandler ShaderAnalysisToolUI.Form "Closing" OnCloseAction
|
|
ShaderAnalysisToolUI.Open() |