try (destroyDialog RsPedDirtBakerRoll) catch () RSTA_LoadCommonFunction #("fn_RSTA_vertexColours.ms") rollout RsPedDirtBakerRoll "Ped Dirt Baker [v1.01:FurtiveJar]" width:260 ( -- Version-phrase generated via: (filein (RsConfigGetWildWestDir() + "script/3dsmax/_config_files/wildwest_header.ms"); print (RsRandomPhrase count:100)) local dirtChan = 14 local rndSeed local noiseContrast = 2.0 local noiseScale = 10 local dirtyRange = (dataPair min:4 max:32) local dirtierRange = (dataPair min:15 max:110) local dirtiestRange = (dataPair min:30 max:127) local dirtierBonesString = "Calf" local dirtiestBonesString = "Foot, Toe" -- VertexPaint colour/size options: local paintClrs = #((color 5 5 5 ), (color 127 127 127 )) local brushSizes = #(0.02, 0.15) -- CONTROLS -- dotNetControl RsBannerPanel "Panel" pos:[0,0] height:32 width:RsPedDirtBakerRoll.width local bannerStruct = makeRsBanner dn_Panel:RsBannerPanel wiki:"PedDirtBaker" filename:(getThisScriptFilename()) button btnDoDirty "Apply Dirt to Selection" tooltip:"Apply Dirt data to vertex-colour channel 14" width:((RsPedDirtBakerRoll.width / 2) - 6) height:30 align:#left offset:[-8, 0] across:3 button btnShowDirty "Show Dirt" tooltip:"Show Dirt Channel (channel 14) on selection" width:((RsPedDirtBakerRoll.width / 4) - 4) height:30 align:#right offset:[25, 0] button btnUnshowDirty "Unshow" tooltip:"Revert view to show materials" width:((RsPedDirtBakerRoll.width / 4) - 6) height:30 align:#right offset:[8, 0] group "Dirt VertexPaint" ( button btnAddVpaint "Add VertexPaint" tooltip:"Apply Dirt VertexPaint modifier to selection (set to channel 14)" height:30 align:#left label lblPaintClr "Paint:" align:#left pos:(btnAddVpaint.pos + [110,1]) radiobuttons rdoPaintClr "" default:0 labels:#("Dark", "Light") align:#right offset:[0,-18] label lblBrushSize "Size:" align:#left pos:(lblPaintClr.pos + [5,16]) radiobuttons rdoBrushSize "" default:0 labels:#("Fine", "Large") align:#right offset:[0,-18] timer tmrVpaint interval:500 active:True ) group "Dirtiness:" ( spinner spnMinDirty "Default: Min:" range:[0, 127, 0] type:#integer fieldWidth:34 align:#right offset:[0,-3] across:2 spinner spnMaxDirty "Max:" range:[0, 127, 0] type:#integer fieldWidth:34 align:#right offset:[0,-3] slider sldMinDirty "" range:[0, 127, 0] type:#integer ticks:0 align:#right offset:[10,-4] across:2 slider sldMaxDirty "" range:[0, 127, 0] type:#integer ticks:0 align:#left offset:[10,-4] editText txtDirtier "Dirtier Bones:" fieldWidth:160 align:#right spinner spnMinDirtier "Dirtier: Min:" range:[0, 127, 0] type:#integer fieldWidth:34 align:#right offset:[0,0] across:2 spinner spnMaxDirtier "Max:" range:[0, 127, 0] type:#integer fieldWidth:34 align:#right offset:[0,0] slider sldMinDirtier "" range:[0, 127, 0] type:#integer ticks:0 align:#right offset:[10,-4] across:2 slider sldMaxDirtier "" range:[0, 127, 0] type:#integer ticks:0 align:#left offset:[10,-4] editText txtDirtiest "Dirtiest Bones:" fieldWidth:160 align:#right spinner spnMinDirtiest "Dirtiest: Min:" range:[0, 127, 0] type:#integer fieldWidth:34 align:#right offset:[0,0] across:2 spinner spnMaxDirtiest "Max:" range:[0, 127, 0] type:#integer fieldWidth:34 align:#right offset:[0,0] slider sldMinDirtiest "" range:[0, 127, 0] type:#integer ticks:0 align:#right offset:[10,-4] across:2 slider sldMaxDirtiest "" range:[0, 127, 0] type:#integer ticks:0 align:#left offset:[10,-4] ) group "Dirt Noise:" ( spinner spnScale "Scale:" range:[0,100,0] scale:0.1 fieldWidth:50 align:#center offset:[0,-3] across:2 spinner spnContrast "Contrast:" range:[0,100,0] scale:0.1 fieldWidth:50 align:#center offset:[0,-3] spinner spnSeed "Seed:" range:[0, 999999, 0] type:#integer fieldWidth:50 align:#center offset:[1,0] across:2 button btnRandom "RND" width:30 height:16 align:#right pos:(spnSeed.pos + [14,0]) ) -- FUNCTIONS -- fn updateCtrls = ( txtDirtier.text = dirtierBonesString txtDirtiest.text = dirtiestBonesString spnMinDirty.value = dirtyRange.min spnMaxDirty.value = dirtyRange.max spnMinDirtier.value = dirtierRange.min spnMaxDirtier.value = dirtierRange.max spnMinDirtiest.value = dirtiestRange.min spnMaxDirtiest.value = dirtiestRange.max sldMinDirty.value = dirtyRange.min sldMaxDirty.value = dirtyRange.max sldMinDirtier.value = dirtierRange.min sldMaxDirtier.value = dirtierRange.max sldMinDirtiest.value = dirtiestRange.min sldMaxDirtiest.value = dirtiestRange.max spnScale.value = noiseScale spnContrast.value = noiseContrast spnSeed.value = rndSeed ) fn showDirty unshow:False = ( local objs = getCurrentSelection() if unshow then ( objs.showVertexColors = False displayColor.shaded = #material ) else ( objs.showVertexColors = True objs.vertexColorType = #map_channel objs.vertexColorMapChannel = dirtChan displayColor.shaded = #object ) completeRedraw() ) fn pedDirtBake objs:(getCurrentSelection()) = ( -- Filter out invalid objs: objs = for obj in objs where (isEditPolyMesh obj) collect obj if (selection.count == 0) do return False -- Generate noise-offset, based on random seed: seed rndSeed local noiseShift = random [-100,-100,-100] [100,100,100] -- Get search-patterns for dirtier/dirtiest bone-names: local dirtierBoneNames = for item in (filterString dirtierBonesString ", ") collect ("*" + item + "*") local dirtiestBoneNames = for item in (filterString dirtiestBonesString ", ") collect ("*" + item + "*") -- Process each object: for obj in objs do ( local skinMod = undefined -- Get Skin modifier: for thisMod in obj.modifiers while (skinMod == undefined) do ( if (isKindOf thisMod skin) do ( skinMod = thisMod ) ) local vertDirts if (skinMod == undefined) then ( -- If object has no Skin modifier, use default dirt-ranges for all verts: vertDirts = for n = 1 to obj.numVerts collect dirtyRange ) else ( -- Get verts used by specified bones: if (getCommandPanelTaskMode() != #modify) do (setCommandPanelTaskMode #modify) modPanel.setCurrentObject skinMod local skinBoneCount = skinOps.getNumberBones skinMod local dirtierBoneNums = #{} local dirtiestBoneNums = #{} -- Find Dirtier/Dirtiest boneNums: for boneNum = 1 to skinBoneCount do ( local skinBoneName = skinOps.getBoneName skinMod boneNum 1 local keepLooking = True for item in dirtierBoneNames while keepLooking do ( if (matchPattern skinBoneName pattern:item) do ( dirtierBoneNums[boneNum] = True keepLooking = True ) ) for item in dirtiestBoneNames while keepLooking do ( if (matchPattern skinBoneName pattern:item) do ( dirtiestBoneNums[boneNum] = True keepLooking = True ) ) ) vertDirts = for skinVertNum = 1 to (skinOps.getNumberVertices skinMod) collect ( local dirtierWeights = #() local dirtiestWeights = #() for vertBoneNum = 1 to (skinOps.getVertexWeightCount skinMod skinVertNum) do ( getBoneID = skinOps.GetVertexWeightBoneID skinMod skinVertNum vertBoneNum local weightList = case of ( (dirtierBoneNums[getBoneID]):dirtierWeights (dirtiestBoneNums[getBoneID]):dirtiestWeights Default:undefined ) if (weightList != undefined) do ( append weightList (skinOps.GetVertexWeight skinMod skinVertNum vertBoneNum) ) ) local hasDirtier = (dirtierWeights.count != 0) local hasDirtiest = (dirtiestWeights.count != 0) local dirtyVals = case of ( -- Find mix between dirtier/dirtiest weightings: (hasDirtier and hasDirtiest): ( local dirtierSum = 0.0 local dirtiestSum = 0.0 for val in dirtierWeights do ( dirtierSum += val ) for val in dirtiestWeights do ( dirtiestSum += val ) local totalSum = (dirtierSum + dirtiestSum) local dirtierMult = (dirtierSum / totalSum) local dirtiestMult = (dirtiestSum / totalSum) local minVal = ((dirtierMult * dirtierRange.min) + (dirtiestMult * dirtiestRange.min)) local maxVal = ((dirtierMult * dirtierRange.max) + (dirtiestMult * dirtiestRange.max)) dataPair min:minVal max:maxVal ) (hasDirtiest):(dirtiestRange) (hasDirtier): ( local dirtierMult = 0.0 for val in dirtierWeights do ( dirtierMult += val ) local dirtyMult = (1.0 - dirtierMult) local minVal = ((dirtierMult * dirtierRange.min) + (dirtyMult * dirtyRange.min)) local maxVal = ((dirtierMult * dirtierRange.max) + (dirtyMult * dirtyRange.max)) dataPair min:minVal max:maxVal ) (Default):(dirtyRange) ) --format "%, % : %\n" hasDirtiest hasDirtier (dirtyVals as string) dirtyVals ) ) local tempObj = RsVertClr_makeTempObj obj copyChan:dirtChan local objOp = RsMeshPolyOp tempObj for vertNum = 1 to vertDirts.count do ( -- Get vert's dirtiness: local dirtRange = vertDirts[vertNum] -- Get vertex position: local vertPos = objOp.getVert tempObj vertNum -- Generate dirtiness-multiplier for this position based on 3d noise values: local noiseVal = noise3 (noiseScale * (vertPos + noiseShift)) -- Scale and clamp value: noiseVal *= noiseContrast noiseVal += 1 noiseVal /= 2 case of ( (noiseVal < 0):(noiseVal = 0) (noiseVal > 1):(noiseVal = 1) ) -- Use noise-value to pick grey from vert's dirt-range: local greyVal = dirtRange.min + (noiseVal * (dirtRange.max - dirtRange.min)) objOp.SetVertColor tempObj dirtChan vertNum [greyVal,greyVal,greyVal] ) RsVertClr_applyTempData tempObj obj dirtChan modName:"DirtColours" ) select objs return OK ) -- VERTEXPAINT MODIFIER BITS -- -- Get currently-active VertexPaint modifier, if shown: local vertMod local vertModEdit = VertexPaintTool() fn getVertMod = ( local getMod = modPanel.getCurrentObject() if (getMod != vertMod) do ( if (isKindOf getMod vertexPaint) then ( vertMod = getMod ) else ( vertMod = undefined ) ) return vertMod ) -- Update controls to match current VertexPaint settings: fn updatePaintCtrls = ( getVertMod() if (vertMod == undefined) then ( rdoPaintClr.enabled = False rdoBrushSize.enabled = False rdoPaintClr.state = 0 rdoBrushSize.state = 0 ) else ( rdoPaintClr.enabled = True rdoBrushSize.enabled = True -- Push buttons if they match relevant value: local setState = 0 local toolPaintClr = (vertModEdit.paintColor as point3) for ctrlNum = 1 to paintClrs.count while (setState == 0) do ( if (distance toolPaintClr (paintClrs[ctrlNum] as point3) < 0.001) do ( setState = ctrlNum ) ) if (rdoPaintClr.state != setState) do (rdoPaintClr.state = setState) local setState = 0 local toolBrushSize = vertModEdit.brushSize for ctrlNum = 1 to brushSizes.count while (setState == 0) do ( if (abs (toolBrushSize - brushSizes[ctrlNum]) < 0.001) do ( setState = ctrlNum ) ) if (rdoBrushSize.state != setState) do (rdoBrushSize.state = setState) ) ) -- Use timer to keep control-states up-to-date with Vertex Paint Toolkit: on tmrVpaint tick do ( updatePaintCtrls() ) -- Change VertexPaint colour: fn pressPaintClrBtn num = ( vertModEdit.paintColor = paintClrs[num] vertModEdit.keepToolboxOpen = vertModEdit.keepToolboxOpen updatePaintCtrls() ) on rdoPaintClr changed state do (pressPaintClrBtn state) -- Change VertexPaint brush-size: fn pressBrushSizeBtn num = ( vertModEdit.brushSize = brushSizes[num] vertModEdit.keepToolboxOpen = vertModEdit.keepToolboxOpen updatePaintCtrls() ) on rdoBrushSize changed state do (pressBrushSizeBtn state) -- Add new VertexPaint modifier to selection: on btnAddVpaint pressed do ( local objs = for obj in getCurrentSelection() where (isEditPolyMesh obj) collect obj if (objs.count == 0) do ( return False ) showDirty() local paintMod = vertexPaint mapChannel:dirtChan name:"Dirt VertexPaint" addModifier objs paintMod showDirty() select objs ) -- /VERTEXPAINT MODIFIER BITS -- on txtDirtier entered newText do ( dirtierBonesString = newText ) on txtDirtiest entered newText do ( dirtiestBonesString = newText ) fn minDirtyChanged val = ( dirtyRange.min = val if (val > dirtyRange.max) do (dirtyRange.max = val) updateCtrls() ) on spnMinDirty changed val do (minDirtyChanged val) on sldMinDirty changed val do (minDirtyChanged val) fn maxDirtyChanged val = ( dirtyRange.max = val if (val < dirtyRange.min) do (dirtyRange.min = val) updateCtrls() ) on spnMaxDirty changed val do (maxDirtyChanged val) on sldMaxDirty changed val do (maxDirtyChanged val) fn minDirtierChanged val = ( dirtierRange.min = val if (val > dirtierRange.max) do (dirtierRange.max = val) updateCtrls() ) on spnMinDirtier changed val do (minDirtierChanged val) on sldMinDirtier changed val do (minDirtierChanged val) fn maxDirtierChanged val = ( dirtierRange.max = val if (val < dirtierRange.min) do (dirtierRange.min = val) updateCtrls() ) on spnMaxDirtier changed val do (maxDirtierChanged val) on sldMaxDirtier changed val do (maxDirtierChanged val) fn minDirtiestChanged val = ( dirtiestRange.min = val if (val > dirtiestRange.max) do (dirtiestRange.max = val) updateCtrls() ) on spnMinDirtiest changed val do (minDirtiestChanged val) on sldMinDirtiest changed val do (minDirtiestChanged val) fn maxDirtiestChanged val = ( dirtiestRange.max = val if (val < dirtiestRange.min) do (dirtiestRange.min = val) updateCtrls() ) on spnMaxDirtiest changed val do (maxDirtiestChanged val) on sldMaxDirtiest changed val do (maxDirtiestChanged val) on btnRandom pressed do ( rndSeed = (random spnSeed.range[1] spnSeed.range[2]) updateCtrls() ) on spnSeed changed val do ( rndSeed = val ) on spnScale changed val do ( noiseScale = val ) on spnContrast changed val do ( noiseContrast = val ) on btnDoDirty pressed do ( local objs = for obj in getCurrentSelection() where (isEditPolyMesh obj) collect obj if (objs.count == 0) do ( messageBox "No geometry is selected" title:"Error: Invalid Selection" return False ) showDirty() pedDirtBake objs:objs showDirty() ) on btnShowDirty pressed do ( showDirty() ) on btnUnshowDirty pressed do ( showDirty unshow:True ) on RsPedDirtBakerRoll open do ( -- Initialise tech-art banner: bannerStruct.setup() rndSeed = (random spnSeed.range[1] spnSeed.range[2]) updateCtrls() ) ) createDialog RsPedDirtBakerRoll