593 lines
16 KiB
Plaintext
Executable File
593 lines
16 KiB
Plaintext
Executable File
filein (RsConfigGetWildWestDir() + "script/3dsmax/_config_files/wildwest_header.ms")
|
|
|
|
|
|
struct FaceStruct
|
|
(
|
|
idx,
|
|
pos,
|
|
normal
|
|
)
|
|
|
|
|
|
struct PxmFurStruct
|
|
(
|
|
mapChannel = 4,
|
|
rayMesh,
|
|
|
|
getVertsUsingFaceOp,
|
|
facesUsingVertOp,
|
|
|
|
rayMesh = undefined,
|
|
processItems = Dictionary(),
|
|
|
|
|
|
fn SelectHiDetailObjects objset =
|
|
(
|
|
local returnList = #()
|
|
|
|
local meshAttr = getattrindex "Gta Object" "Mesh"
|
|
|
|
for object = 1 to objset.count do
|
|
(
|
|
if (getattrclass objset[object]) == "Gta Collision" then
|
|
(
|
|
continue
|
|
)
|
|
|
|
if (isRefObj objset[object]) then
|
|
(
|
|
continue
|
|
)
|
|
|
|
|
|
local found = (findstring (tolower objset[object].name) "lod")
|
|
|
|
if (found != undefined) then
|
|
(
|
|
continue
|
|
)
|
|
|
|
if (RsGetObjLodDistance idxLodDistance) > 300 then
|
|
(
|
|
continue
|
|
)
|
|
|
|
--check the attributes to make sure they're not reflection proxies or somesuch
|
|
if ((getattrclass objset[object]) == "Gta Object") and (GetAttr objset[object] meshAttr == false) then
|
|
(
|
|
continue
|
|
)
|
|
else
|
|
(
|
|
append returnList objset[object]
|
|
)
|
|
|
|
if (objset[object].children != undefined) then
|
|
(
|
|
join returnList (SelectHiDetailObjects objset[object].children)
|
|
)
|
|
|
|
|
|
)
|
|
|
|
returnList
|
|
),
|
|
|
|
|
|
/***
|
|
|
|
***/
|
|
fn addDebugTiming name start end =
|
|
(
|
|
if _DEBUG_ then
|
|
(
|
|
append timings (DataPair id:name time:(end - start))
|
|
)
|
|
),
|
|
|
|
|
|
/***
|
|
|
|
***/
|
|
fn findBorderVerts obj faces =
|
|
(
|
|
local faceBitArray = faces as BitArray
|
|
local faceVerts = getVertsUsingFaceOp obj faceBitArray
|
|
|
|
local borderVerts = #()
|
|
for vert in faceVerts do
|
|
(
|
|
vertFaces = facesUsingVertOp obj vert
|
|
|
|
--if all the faces are not in selected faces then its a border vert
|
|
local diff = (faceBitArray * vertFaces).numberSet
|
|
|
|
if diff < vertFaces.numberSet then
|
|
(
|
|
append borderVerts vert
|
|
)
|
|
)
|
|
|
|
--return
|
|
borderVerts
|
|
),
|
|
|
|
|
|
/***
|
|
|
|
***/
|
|
fn PxmFadeUnderFur =
|
|
(
|
|
--timings = #()
|
|
gRsUlog.init "Fade Under Decals"
|
|
|
|
--check for any meshes without collapsed stacks
|
|
for item in selection where (item.modifiers.count > 0) do
|
|
(
|
|
messageBox (item.name + " has an uncollapsed stack, it will be ignored.\nCollapse it and run the tool again for it to be included")
|
|
)
|
|
|
|
--find all SELECTED meshes with decal shaders applied
|
|
local meshes = for item in selection where ((ClassOf item == Editable_mesh) or (classOf item == Editable_poly)) collect item
|
|
local furMeshes = #()
|
|
for item in meshes do
|
|
(
|
|
local meshShaders = RsGetShaderListForObjects item
|
|
|
|
for shd in meshShaders where ((matchPattern shd pattern:"*_fur_*.sps") == true) do
|
|
(
|
|
appendifunique furMeshes (DataPair mesh:item shaders:meshShaders)
|
|
)
|
|
)
|
|
|
|
|
|
local Meshes_and_Faces = Dictionary()
|
|
|
|
--for each mesh go through the faces and look for the mesh beneath it
|
|
escapeenable = true
|
|
|
|
--format "decal mesh count: % \n" furMeshes.count
|
|
counter = 1.0
|
|
mainProgress = RSProgressWindow Title:"[Stage 1]: Processing Fur Meshes..." StartStep:1 EndStep:furMeshes.count
|
|
mainProgress.Start()
|
|
|
|
for dm in furMeshes do
|
|
(
|
|
--find any other mesh BBox that intersect this one to ray test faces of
|
|
--this is to cut down the number of objects checked.
|
|
local localNodesToRayTest = for item in objects where (item != dm.mesh) and
|
|
(GetAttrClass item == "Gta Object") and
|
|
(intersects item dm.mesh) and
|
|
(classOf item == Editable_mesh) or (classof item == Editable_Poly) collect item
|
|
|
|
--Find the HD meshes that we want to effect
|
|
local localHDNodes = SelectHiDetailObjects localNodesToRayTest
|
|
|
|
append localNodesToRayTest dm.mesh
|
|
|
|
local faceFunc = undefined
|
|
local faceNormFunc = undefined
|
|
local meshType = undefined
|
|
if (classOf dm.mesh == Editable_mesh) then
|
|
(
|
|
meshType = #mesh
|
|
faceFunc = meshop.getFaceCenter
|
|
faceNormFunc = getFaceNormal
|
|
)
|
|
else if (classOf dm.mesh == Editable_Poly) then
|
|
(
|
|
meshType = #poly
|
|
faceFunc = polyop.getFaceCenter
|
|
faceNormFunc = polyop.getFaceNormal
|
|
)
|
|
|
|
--get the faces with the fur shaders applied
|
|
local faceCollection = #()
|
|
for shd in dm.shaders where (matchPattern shd pattern:"*_fur_*") do
|
|
(
|
|
local facelist = #()
|
|
RsGetObjFacesUsingShader dm.mesh (getfilenamefile shd) faceList
|
|
join faceCollection facelist
|
|
)
|
|
|
|
subProgress = mainProgress.StartSubProgress 0 faceCollection.count SubTitle:"Find occluded meshes.."
|
|
subProgress.Start()
|
|
|
|
|
|
for face in faceCollection do
|
|
(
|
|
--find the closest mesh to the facePos
|
|
--local sFace = FaceStruct()
|
|
local sFace = DataPair pos:undefined normal:undefined
|
|
|
|
sFace.normal = faceNormFunc dm.mesh face
|
|
sFace.pos = faceFunc dm.mesh face node:dm.mesh
|
|
|
|
--push the position away from the face a little to get more ray intersection hits
|
|
sFace.pos += (0.2 * sFace.normal)
|
|
|
|
local results = for node in localHDNodes where
|
|
(
|
|
local bounds = nodeGetBoundingBox node (matrix3 1)
|
|
(sFace.pos.x > bounds[1].x) and (sFace.pos.x < bounds[2].x) and
|
|
(sFace.pos.y > bounds[1].y) and (sFace.pos.y < bounds[2].y) and
|
|
(sFace.pos.z > bounds[1].z) and (sFace.pos.z < bounds[2].z)
|
|
)
|
|
collect node
|
|
|
|
--Find the HD meshes that we want to effect
|
|
--so we need to get all the faces realted to each node the face intersects and get thme in terms of the nodes
|
|
for node in results do
|
|
(
|
|
if (processItems.hasKey node) then
|
|
(
|
|
local processFaces = processItems.getValue node
|
|
append processFaces sFace
|
|
|
|
processItems.setValue node processFaces
|
|
)
|
|
else
|
|
(
|
|
processItems.addKey node #(sFace)
|
|
)
|
|
)
|
|
subProgress.PostProgressStep()
|
|
)
|
|
subProgress.End()
|
|
|
|
mainProgress.PostProgressStep()
|
|
)
|
|
mainProgress.End()
|
|
|
|
--Could be multiple hits, say if there is overlapping mesh geo.
|
|
--Find the faces in each that are not decal shaders
|
|
--Also check that the underlying shader on the face is a pxm type
|
|
mainProgress = RSProgressWindow Title:"[Stage 2]: Test occluded meshes ....." StartStep:1 EndStep:(processItems.keys()).count
|
|
mainProgress.Start()
|
|
|
|
rayMesh = RayMeshGridIntersect()
|
|
for node in processItems.keys() do
|
|
(
|
|
rayMesh.Initialize 10
|
|
rayMesh.addNode node
|
|
rayMesh.BuildGrid()
|
|
|
|
local triPolyLookup
|
|
local nodeType
|
|
if (classOf node == Editable_Poly) or (classOf node == PolyMeshObject) then
|
|
(
|
|
nodeType = #poly
|
|
triPolyLookup = RsMakeTriToPolyList node
|
|
)
|
|
else if (classOf node == Editable_mesh) then
|
|
(
|
|
nodeType = #mesh
|
|
)
|
|
|
|
|
|
faceProgress = mainProgress.StartSubProgress 0 (processItems.getValue node).count SubTitle:(node.name + " Faces..")
|
|
faceProgress.Start()
|
|
for face in (processItems.getValue node) do
|
|
(
|
|
--fire a ray
|
|
local hits = rayMesh.intersectRay face.pos (-face.normal) false
|
|
--local hits = rayMesh.closestFace face.pos
|
|
|
|
local faceIdx = 0
|
|
if (hits > 0) then
|
|
(
|
|
--get the face hit
|
|
local theIndex = rayMesh.getClosestHit() --get the index of the closest hit by the ray
|
|
|
|
faceIdx = rayMesh.getHitFace theIndex
|
|
|
|
local matID = 0
|
|
|
|
|
|
if nodeType == #poly then
|
|
(
|
|
faceIdx = triPolyLookup[faceIdx]
|
|
matID = polyop.getfacematid node faceIdx
|
|
)
|
|
else if nodeType == #mesh then
|
|
(
|
|
matID = getFaceMatID node faceIdx
|
|
)
|
|
|
|
|
|
--matID == 0 check
|
|
if (matID != 0) then
|
|
(
|
|
--get the shader for the face material
|
|
--check the material on the face
|
|
--if its a decal type we neednt bother with it, continue to the next in the loop
|
|
--local faceShader = undefined
|
|
local validShader = true
|
|
local shaderName = undefined
|
|
if (classof node.material) == MultiMaterial then
|
|
(
|
|
--check its a RageShader
|
|
if (classOf node.material.materialList[matID] == Rage_Shader) then
|
|
(
|
|
shaderName = RstGetShaderName node.material.materialList[matID]
|
|
)
|
|
)
|
|
else if (classof node.material == Rage_Shader) then
|
|
(
|
|
shaderName = RstGetShaderName node.material
|
|
)
|
|
else
|
|
(
|
|
validShader = false
|
|
)
|
|
|
|
if (shaderName != undefined) then
|
|
(
|
|
if (matchPattern shaderName pattern:"*_fur_*") then
|
|
(
|
|
validShader = false
|
|
)
|
|
|
|
)
|
|
|
|
--save to dictionary
|
|
if validShader then
|
|
(
|
|
local key = node.name
|
|
if (Meshes_and_Faces.hasKey key) then
|
|
(
|
|
local currentVal = Meshes_and_Faces.getValue key
|
|
append currentVal faceIdx
|
|
Meshes_and_Faces.setValue key currentVal
|
|
)
|
|
else
|
|
(
|
|
Meshes_and_Faces.addKey key #{faceIDx}
|
|
)
|
|
)
|
|
)
|
|
|
|
)
|
|
faceProgress.PostProgressStep()
|
|
)
|
|
faceProgress.End()
|
|
|
|
|
|
rayMesh.free()
|
|
mainProgress.PostProgressStep()
|
|
)
|
|
--reset the dictionary
|
|
processItems = Dictionary()
|
|
|
|
mainProgress.End()
|
|
|
|
|
|
|
|
--/////////////////////////
|
|
--STAGE 2
|
|
--/////////////////////////
|
|
|
|
--Now process the gathered data and paint out the faces
|
|
|
|
mainProgress = RSProgressWindow Title:"[Stage 3]: Modify POM Strength..." StartStep:0 EndStep:(Meshes_and_Faces.keys()).count
|
|
mainProgress.Start()
|
|
|
|
for obj in Meshes_and_Faces.keys() do
|
|
(
|
|
objNode = (getNodeByName obj)
|
|
if objNode == undefined then
|
|
(
|
|
print "undefined objNode!"
|
|
continue
|
|
)
|
|
local facesOfInterest = Meshes_and_Faces.getValue obj
|
|
objNode.selectedFaces = facesOfInterest --as bitArray
|
|
|
|
local setMapVertFunc = undefined
|
|
local setFaceColorOp = undefined
|
|
local faceMapFunc = meshop.getMapFace
|
|
local getMapFaceOp = RsGetMapFaceFunc objNode
|
|
local getFaceVerts = RsGetFaceFunc objNode
|
|
local getMapSupportOP = undefined
|
|
|
|
if (classOf objNode == Editable_mesh) then
|
|
(
|
|
--getFaceVerts = getface
|
|
getMapVertFunc = meshop.getMapVert
|
|
setMapVertFunc = meshop.setMapVert
|
|
getVertsUsingFaceOp = meshop.getVertsUsingFace
|
|
setFaceColorOp = meshop.setFaceColor
|
|
--getMapFaceOp = meshop.getMapVertsUsingMapFace
|
|
facesUsingVertOp = meshop.getFacesUsingVert
|
|
getVertsUsingFaceOp = meshop.getVertsUsingFace
|
|
getMapSupportOP = meshop.getMapSupport
|
|
)
|
|
else if (classof objNode == Editable_Poly) or (classof objNode == PolyMeshObject) then
|
|
(
|
|
--getFaceVerts = polyop.getFaceVerts
|
|
getMapVertFunc = polyop.getMapVert
|
|
setMapVertFunc = polyop.setMapVert
|
|
getVertsUsingFaceOp = polyop.getVertsUsingFace
|
|
setFaceColorOp = polyop.setFaceColor
|
|
--getMapFaceOp = polyop.getMapFace
|
|
facesUsingVertOp = polyop.getFacesUsingVert
|
|
getVertsUsingFaceOp = polyop.getVertsUsingFace
|
|
getMapSupportOP = polyop.getMapSupport
|
|
)
|
|
|
|
--NEED TO CHECK MAP SUPPORT FOR CHANNEL 4
|
|
if (getMapSupportOP objNode mapChannel) == false then
|
|
(
|
|
format "Object: % missing % \n" objNode mapChannel
|
|
gRsUlog.logWarning ("Object: " + objNode.name +" is missing map channel 4, no fade applied")
|
|
mainProgress.PostProgressStep()
|
|
continue
|
|
)
|
|
|
|
|
|
local mapVertLookup = for n = 1 to objNode.verts.count collect #{}
|
|
|
|
-- Build geometry-to-UV vertex-lookup list, to allow us to find equivalent indexes quickly...
|
|
subProgress = mainProgress.StartSubProgress 0 objNode.faces.count SubTitle:"Build vertex lookup.."
|
|
subProgress.Start()
|
|
for FaceNum = 1 to objNode.Faces.count do
|
|
(
|
|
--local faceIdx = objNode.selectedFaces[FaceNum].index
|
|
local GeomFaceVerts = getFaceVerts objNode FaceNum
|
|
|
|
local MapFaceVerts = getMapFaceOp objNode mapChannel FaceNum
|
|
|
|
|
|
for n = 1 to GeomFaceVerts.Count do
|
|
(
|
|
mapVertLookup[GeomFaceVerts[n]][MapFaceVerts[n]] = True
|
|
)
|
|
subProgress.PostProgressStep()
|
|
)
|
|
subProgress.End()
|
|
|
|
local mapVertsDone = #{}
|
|
|
|
with undo off
|
|
(
|
|
--first fill all the faces with red
|
|
subProgress = mainProgress.StartSubProgress 0 facesOfInterest.count SubTitle:"Paint Faces.."
|
|
subProgress.Start()
|
|
|
|
for face in facesOfInterest do
|
|
(
|
|
for fv in (getFaceVerts objNode face) do
|
|
(
|
|
local mapVerts = mapVertLookup[fv]
|
|
|
|
for mv in mapVerts where (not mapVertsDone[mv]) do
|
|
(
|
|
local currentColour = getMapVertFunc objNode mapChannel mv
|
|
setMapVertFunc objNode mapChannel mv [1.0, currentColour.y, currentColour.z]
|
|
)
|
|
)
|
|
subProgress.PostProgressStep()
|
|
)
|
|
subProgress.End()
|
|
|
|
|
|
--then get the border ones and do the fade
|
|
local borderVerts = findBorderVerts objNode facesOfInterest
|
|
|
|
local mapVerts = #()
|
|
|
|
local vtxCounter = 1
|
|
for vtx in borderVerts do
|
|
(
|
|
join mapVerts mapVertLookup[vtx]
|
|
vtxCounter += 1
|
|
)
|
|
|
|
--set to red
|
|
subProgress = mainProgress.StartSubProgress 0 mapVerts.count SubTitle:"Paint Border.."
|
|
subProgress.Start()
|
|
|
|
mapVertsDone = #{}
|
|
mapVerts = makeUniqueArray mapVerts
|
|
for mv in mapVerts where (not mapVertsDone[mv]) do
|
|
(
|
|
--get the current colour value, we only want to adjust the red
|
|
local currentColour = getMapVertFunc objNode mapChannel mv
|
|
setMapVertFunc objNode mapChannel mv [1.0, currentColour.y, currentColour.z]
|
|
|
|
mapVertsDone[mv] = true
|
|
|
|
subProgress.PostProgressStep()
|
|
)
|
|
subProgress.End()
|
|
|
|
update objNode
|
|
|
|
if (classof objNode == Editable_Poly) or (classof objNode == PolyMeshObject) then
|
|
(
|
|
polyop.collapseDeadStructs objNode
|
|
)
|
|
)
|
|
|
|
mainProgress.PostProgressStep()
|
|
)
|
|
mainProgress.End()
|
|
|
|
Meshes_and_Faces = undefined
|
|
gc light:true
|
|
|
|
ForceCompleteRedraw()
|
|
|
|
gRsUlog.Validate()
|
|
|
|
true
|
|
)
|
|
)
|
|
|
|
|
|
--/////////////////////////////////////////
|
|
-- UI
|
|
--/////////////////////////////////////////
|
|
try(destroyDialog PxmFadeUnderFurUI)catch()
|
|
rollout PxmFadeUnderFurUI "Fade Pxm Under Fur Grass" width:250 height:70
|
|
(
|
|
--/////////////////////////////////////////
|
|
-- VARIABLES
|
|
--/////////////////////////////////////////
|
|
local _DEBUG_ = false
|
|
local timings = #()
|
|
|
|
local mapChannel = 4
|
|
|
|
local pxmFurTool = PxmFurStruct()
|
|
|
|
--/////////////////////////////////////////
|
|
-- CONTROLS
|
|
--/////////////////////////////////////////
|
|
--dotNetControl rsBannerPanel "Panel" pos:[0,0] height:32 width:(PxmFadeUNderDecalsUI.width)
|
|
--local banner = makeRsBanner dn_Panel:rsBannerPanel versionName:"Pumpkin Trump" versionNum:0.14 wiki:"POM Fade Under Decals" filename:(getThisScriptFilename())
|
|
timer ctrlState interval:5000 active:false
|
|
dotNetControl btnProcessSelected "Button" text:"Process Selected" width:(PxmFadeUnderFurUI.width - 10) height:dnStyle.stdButtonHeight offset:[-10, 0]
|
|
|
|
--/////////////////////////////////////////
|
|
-- FUNCTIONS
|
|
--/////////////////////////////////////////
|
|
|
|
|
|
--/////////////////////////////////////////
|
|
-- EVENTS
|
|
--/////////////////////////////////////////
|
|
|
|
/***
|
|
|
|
***/
|
|
on btnProcessSelected click do
|
|
(
|
|
dnStyle.setExecuteStyle btnProcessSelected
|
|
local success = pxmFurTool.PxmFadeUnderFur()
|
|
ctrlState.active = true
|
|
if success then dnStyle.setSuccessStyle btnProcessSelected else dnStyle.setErrorStyle btnProcessSelected
|
|
)
|
|
|
|
/***
|
|
switch state timer
|
|
***/
|
|
on ctrlState tick do
|
|
(
|
|
dnStyle.setDormantStyle btnProcessSelected
|
|
ctrlState.active = false
|
|
)
|
|
|
|
--/////////////////////////////////////////
|
|
--
|
|
--/////////////////////////////////////////
|
|
on PxmFadeUnderFurUI open do
|
|
(
|
|
--banner.setup()
|
|
dnStyle.initButtonStyle btnProcessSelected
|
|
)
|
|
|
|
|
|
)
|
|
--createDialog PxmFadeUnderFurUI
|