-- Rockstar Mesh Tinting Tool -- Rockstar North -- 01/2011 -- by Adam Munson -- callbacks.removeScripts id:#meshTintTool try (destroyDialog RsMeshTintToolRoll) catch () filein "pipeline/export/maps/fragment.ms" filein "pipeline/helpers/materials/meshTint_funcs.ms" RSTA_LoadCommonFunction #("fn_RSTA_vertexColours.ms") global RsMeshTintDebug = False global RsMeshTintToolRoll global idxIsFragment = getAttrIndex "Gta Object" "Is Fragment" global idxUserDefinedPalette = getAttrIndex "Gta Object" "User Defined Palette" -- Main mesh tinter struct that stores the object's MeshTint data: struct paletteStruct ( toolName = "Mesh Tint Tool", -- Phrase generated via: (filein (RsConfigGetWildWestDir() + "script/3dsmax/_config_files/wildwest_header.ms"); print (RsRandomPhrase count:100)) -- Set to True if palette matches mesh, or edits have been made to default palette palMatches = True, palEdited = False, -- Used to deactivate selection-event callbacks: dontAllowCallbacks = false, -- Current-prop data: propName, selObj, rootObj, tintedObjs = #(), selObjNum = 1, palFilename, tempPalFilename, isLodObj = False, hdObj, hdPalFilename, paletteRows = #(), -- Colour-rows loaded from palette-file selectedRow = 1, -- Currently-selected palette-index objPalIndexes = #(), -- Palette-indices, per tri-vert, per mesh in tintedObjs -- Details of palette found on object(s) before any edits were made: origDefaultPalette, origObjPalIndexes, tintChannel = gRsMeshTintFuncs.tintChannel, -- Channel-paste modifiers are given these names: defaultModName = "MeshTint:MainPalette", tempModName = "MeshTint:Temp", fragSuffix = "_frag_", -- Used to tell weapon-scenes apart from other scenes: weaponPath = (RsMakeBackSlashes (RsConfigGetArtDir() + "Models/Props/Weapons/*")), ------------------------------------------------------------------------------------------------------------------------------------ --FUNCTIONS ------------------------------------------------------------------------------------------------------------------------------------ -- Get temp/non-temp palette-filename for obj: fn getFileName temp:false = ( if temp then tempPalFilename else palFilename ), fn setFilenames objName = ( propName = (RsStripSuffix objName fragSuffix) palFilename = gRsMeshTintFuncs.getPaletteFilename objName:propName temp:False tempPalFilename = gRsMeshTintFuncs.getPaletteFilename objName:propName temp:True -- Get palette for hd-object, if this is a lod-object: hdPalFilename = case of ( isLodObj: ( gRsMeshTintFuncs.getPaletteFilename objName:(RsStripSuffix hdObj.name fragSuffix) temp:False ) default:undefined ) -- If object's palette was set up with a non-fixed frag-suffix, -- offer to copy the frag-suffix palette to the correct path: if (not doesFileExist palFilename) do ( local fragFilename = gRsMeshTintFuncs.getPaletteFilename objName:(propName + fragSuffix) temp:False if (doesFileExist fragFilename) do ( local msg = stringStream "" format "File % doesn't exist, but a frag-suffixed version does.\n\nDo you want to use a copy of % (recommended) or generate a new palette-file?" (filenameFromPath palFilename) (filenameFromPath fragFilename) to:msg local queryVal = RsQueryBoxMultiBtn (msg as string) title:"MeshTint Palette Update" timeout:-1 defaultBtn:1 labels:#("Copy Frag-Palette", "Generate New", "Cancel") case queryVal of ( 1: ( copyFile fragFilename palFilename ::RsMeshTintToolRoll.setSavedStatusClr setPalChanged:True ) 3: ( return False ) ) ) ) return True ), -- Set all tint-materials used on current prop's meshes to use palFilename: fn setTexmapsToUsePalette = ( if not isValidNode rootObj do return False -- Make sure rootObj is set to use user-defined palette: setAttr rootObj idxUserDefinedPalette True for obj in tintedObjs where (isValidNode obj) do ( -- Get materials used on object's faces: local objMats = RsGetMaterialsOnObjFaces obj for mat in objMats where (isKindOf mat Rage_Shader) and (matchPattern (RstGetShaderName mat) pattern:"*_tnt*") do ( local numVars = RstGetVariableCount mat local findTexMap = True for varNum = 1 to numVars while findTexMap do ( if (RsIsTintPalette (RstGetVariableName mat varNum)) do ( findTexMap = False -- Apply the user defined palette to the shader materials RstSetVariable mat varNum palFilename ) ) ) ) return True ), -- Collect fragment-mesh hierarchy as a list: fn getHierarchy obj = ( local outList = #() TraverseHierarchyRec obj CollectMeshes outList return outList ), -- Get prop-data for selected object -- Determine root-mesh for selObj and get associated meshes as array tintedObjs fn getObjData debugFuncSrc:"" debugTabs:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[getObjData]<=[%]\n" debugTabs debugFuncSrc) -- Set default values... selObj = selection[1] rootObj = undefined -- This prop's prime object tintedObjs = #() -- Objects used to generate palettes, these objects will all be given vertex-colour modifiers selObjNum = 1 propName = palFilename = tempPalFilename = hdPalFilename = undefined origDefaultPalette = undefined origObjPalIndexes = undefined if (selection.count != 1) do (return False) -- If frag-proxy is selected, then choose its root-object: local fragRootObj = getNodeByName (selObj.name + fragSuffix) if (fragRootObj != undefined) then ( rootObj = fragRootObj ) else ( rootObj = selObj ) -- Find the root-object, if it's not the current selection. -- Traverse up through link-hierarchy to parent-geometry (if a child object is selected) while (isKindOf rootObj.parent GeometryClass) do ( rootObj = rootObj.parent ) -- If this is a lod, find the HD model (so we can access its palette too) ( -- Weapon lods are linked via suffix... local isWeaponPath = (matchPattern maxFilePath pattern:weaponPath) if isWeaponPath then ( -- If this is a Weapon scene, strip off that L1-suffix, and see if there's a matching non-suffixed object here: if (matchPattern selObj.name pattern:"*_L?") do ( local hdName = (substring selObj.name 1 (selObj.name.count - 3)) hdObj = getNodeByName hdName ) ) else ( -- Traverse down lod-child links: -- (we're assuming there's only one, what with this being a prop-source tool) local findHdObj = rootObj local walkArray = #(findHdObj) while (walkArray.count != 0) do ( findHdObj = walkArray[1] walkArray.count = 0 RsSceneLink.GetChildren -1 findHdObj &walkArray ) if (findHdObj != rootObj) do ( hdObj = findHdObj ) ) isLodObj = (hdObj != undefined) ) -- Get propName and palette-filenames for this prop, now we have its root-object: if not (setFilenames rootObj.name) do return False -- If this is a frag-root object, compile a list of its child-meshes: if (getAttrClass rootObj == "Gta Object") and (getAttr rootObj idxIsFragment == True) then ( tintedObjs = heirarchyWalk rootObj ) else ( tintedObjs = #(rootObj) ) -- Filter out non-geometry objs... tintedObjs = for obj in tintedObjs where (isValidNode obj) and (isKindOf obj GeometryClass) collect obj selObjNum = findItem tintedObjs selObj if RsMeshTintDebug do ( format "%|-------tintedObjs: \n" debugTabs for obj in tintedObjs do ( format "%\t%\n" debugTabs(obj as string) ) format "%-------|\n" debugTabs ) return (tintedObjs.count != 0) ), -- Generates a palette-file for the object, and returns the palette-indexes for its tri-verts per-object -- (If temp, the existing data and struct-values are not overwritten) fn generatePalette temp:false debugFuncSrc:"" debugTabs:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[generatePalette]<=[%] (temp:% )]\n" debugTabs debugFuncSrc temp) local filename = getFileName temp:temp if not temp do ( if not (gRsPerforce.readOnlyP4Check #(fileName) exclusive:false) do return false ) local palWidth = gRsMeshTintFuncs.paletteWidth local palMidWidth = gRsMeshTintFuncs.paletteWidth -- Filter out any objects that have gone missing... tintedObjs = for obj in tintedObjs where (isValidNode obj) collect obj selObjNum = findItem tintedObjs selObj local meshes = for obj in tintedObjs collect (copy obj.mesh) -- Ensure that top of modifier-stack is shown: showEndResult = true if RsMeshTintDebug do (format "%\trexGenerateColourPalette: paletteWidth:% paletteMinWidth:% filename: % objs:%\n" debugTabs palWidth palMidWidth fileName (objs as string)) -- Generate palette-file, get palette-indexes for each map-vert: local rexIdxList = rexGenerateColourPalette meshes palWidth palMidWidth fileName --print ((makeUniqueArray rexIdxList) as string) -- Split up per-face-vert palette-index up per mesh: local vertIdx = 0 local retVal = for thisMesh in meshes collect ( local meshPalIdxs = #() for meshTriNum = 1 to thisMesh.numFaces do ( for n = 1 to 3 do ( vertIdx += 1 -- Convert from 0-based: append meshPalIdxs (rexIdxList[vertIdx] + 1) ) ) meshPalIdxs ) -- Store details of prop's initial exploratory palette-regen: if (origDefaultPalette == undefined) do ( origDefaultPalette = (gRsMeshTintFuncs.loadPalettesFromFile fileName)[1] origObjPalIndexes = for item in retVal collect (deepCopy Item) if RsMeshTintDebug do ( format "%\t\tOriginal mesh palette: %\n" debugTabs (origDefaultPalette as string) format "%\t\tOriginal mesh idxs: %\n" debugTabs (origObjPalIndexes as string) ) ) -- Add palette to Perforce, store index-list to this data-struct: if not temp do ( objPalIndexes = retVal gRsPerforce.postExportAdd() ) if RsMeshTintDebug do (format "%[/generatePalette]\n" debugTabs) return retVal ), -- Saves out the palette-file: fn savePalettes palettes: filename: temp:false debugFuncSrc:"" debugTabs:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[savePalettes]<=[%] filename: %, palettes:%]\n" debugTabs debugFuncSrc (filename as string) (palettes as string)) if (filename == unsupplied) do (filename = getFilename temp:temp) if (palettes == unsupplied) do (palettes = paletteRows) if not temp do ( format "Saving file: %\n" fileName if not (gRsPerforce.readOnlyP4Check #(fileName) exclusive:false) do return false ) -- Palette-file can only contain a power-of-two number of rows. -- (Undefined rows are output as copies of first row) local bmpHeight = 4 while (palettes.count > bmpHeight) do ( bmpHeight *= 2 ) local bmpWidth = 256 -- Generate null-coloured default bitmap: local saveBmp = bitmap bmpWidth bmpHeight filename:filename gamma:1.0 color:RsTintPaletteNullColour local defaultRow = #() -- Process each palette row: for rowNum = 1 to bmpHeight do ( -- Use default palette, if row is undefined: local palNum = if (rowNum > palettes.count) then 1 else rowNum local pixelArray = if (palettes.count == 0) then #() else (deepCopy palettes[palNum]) if (palNum == 1) then ( defaultRow = pixelArray ) else ( -- Make sure all palettes are same length as default row, taking colours from that row if they need extending. case of ( (pixelArray.count > defaultRow.count):(pixelArray.count = defaultRow.count) (pixelArray.count < defaultRow.count): ( local addPixels = for n = (pixelArray.count + 1) to defaultRow.count collect defaultRow[n] join pixelArray addPixels ) ) ) -- Set palette's pixel-row: setPixels saveBmp [0, (rowNum - 1)] pixelArray ) save saveBmp close saveBmp if not temp do ( -- Set default-data arrays to saved info: origDefaultPalette = deepCopy palettes[1] origObjPalIndexes = for item in objPalIndexes collect (deepCopy Item) -- Set all prop-materials to use this saved filename: setTexmapsToUsePalette() -- Add new file to Perforce, if queued by readOnlyP4Check: gRsPerforce.postExportAdd() ::RsMeshTintToolRoll.setSavedStatusClr setPalChanged:False ) if RsMeshTintDebug do (format "%[/savePalettes]\n" debugTabs) return true ), -- Generate list of palette-indices in paletteB that correspond to colours in paletteA: fn getOldNewLookupList paletteA paletteB debugFuncSrc:"" debugTabs:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[getOldNewLookupList]<=[%]\n\t%paletteA: %\n\t%paletteB: %\n" debugTabs debugFuncSrc debugTabs (paletteA as string) debugTabs (paletteB as string)) -- Collect colours as CIE-L*ab points, so we can compare by distance to find perceptually-closest colours: local oldVals = for clr in paletteA while (clr != undefined) collect (RsXYZtoCIELAB (RsRGBtoXYZ clr)) local newVals = for clr in paletteB while (clr != undefined) collect (RsXYZtoCIELAB (RsRGBtoXYZ clr)) local retVal = for oldIdx = 1 to oldVals.count collect ( local oldClr = oldVals[oldIdx] -- Find closest matching colour: local clrDists = for newClr in newVals collect (distance oldClr newClr) local minDist = (aMin clrDists) local findNum = findItem clrDists minDist findNum ) return retVal ), -- Remove non-base tint-channel palette-paste modifiers from tintedObjs: fn removeTempModifiers debugFuncSrc:"" debugTabs:"" = ( if not isValidNode selObj do return false if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[removeTempModifiers]<=[%]\n" debugTabs debugFuncSrc) for thisObj in tintedObjs do ( -- Collect matching modifiers: local removeMods = for thisMod in thisObj.modifiers where ( (isKindOf thisMod UVW_Mapping_Paste) and (thisMod.mapId == tintChannel) and (not (matchPattern thisMod.name pattern:defaultModName)) ) collect thisMod -- Remove modfiers: for thisMod in removeMods do ( deleteModifier thisObj thisMod ) ) if RsMeshTintDebug do (format "%[/removeTempModifiers]\n" debugTabs) ), -- Sets the colours on the object(s) to show it with a particular palette: fn paintPaletteToMeshes row:1 debugFuncSrc:"" debugTabs:"" doSuspend:True = ( local paletteColours = paletteRows[row] if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[paintPaletteToMeshes]<=[%] row:% dontAllowCallbacks:%\n\tpaletteColours: %" debugTabs debugFuncSrc row dontAllowCallbacks debugTabs (paletteColours as string)) if (paletteColours != undefined) do ( -- Convert colours to UV-values: local clrPoints = for clr in paletteColours collect ((clr as point3) / 255.0) local isFirstRow = (row == 1) local dontAllowCallbacksWas = dontAllowCallbacks dontAllowCallbacks = true -- Get current modifier-tab selection: local selPanel = modPanel.getCurrentObject() local subObjWas = subObjectLevel if doSuspend do ( suspendEditing() ) for objNum = 1 to tintedObjs.count do ( local thisObj = tintedObjs[objNum] local objPalIndex = objPalIndexes[objNum] undo off ( -- Create working-object with active tintchannel: local tempObj = RsVertClr_makeTempObj thisObj copyChan:tintChannel local objOp = RsMeshPolyOp tempObj -- Set default mapping for each face: (set to magenta to show up errors) local magenta = red + blue objOp.SetFaceColor tempObj tintChannel #all magenta -- Collect tri-vert-to-map-vert list, which corresponds with objPalIndex: local meshVertIdxList = #() local objMesh = if (objOp == meshOp) then tempObj else (copy tempObj.mesh) local mapTriVerts = for triNum = 1 to objMesh.numFaces collect (RsMeshGetMapFace objMesh tintChannel triNum) for item in mapTriVerts do (join meshVertIdxList item) -- Don't set colour for individual verts more than once: local vertsSet = #{} vertsSet.count = objOp.getNumMapVerts tempObj tintChannel case objOp of ( meshOp: ( for mapVertIdx = 1 to meshVertIdxList.count do ( local mapVertNum = meshVertIdxList[mapVertIdx] if not vertsSet[mapVertNum] do ( local palIdx = objPalIndex[mapVertIdx] local tintClr = clrPoints[palIdx] objOp.setMapVert tempObj tintChannel mapVertNum tintClr vertsSet[mapVertNum] = true ) ) ) -- Mapping-vert nums need to be converted from tri-versions for poly objects: polyOp: ( local mapFaceVerts = for faceNum = 1 to tempObj.numFaces collect (polyOp.getMapFace tempObj tintChannel faceNum) local triVertsList = for triNum = 1 to objMesh.numFaces collect (getFace objMesh triNum) local polyVertsList = for faceNum = 1 to tempObj.numFaces collect (polyOp.getFaceVerts tempObj faceNum) local triToPolyList = RsMakeTriToPolyList tempObj local mapVertIdx = 0 for triNum = 1 to mapTriVerts.count do ( -- Find which poly this tri belongs to: local triPolyNum = triToPolyList[triNum] local triVerts = triVertsList[triNum] local polyVerts = polyVertsList[triPolyNum] local polyMapVerts = mapFaceVerts[triPolyNum] for triVertNum = 1 to 3 do ( mapVertIdx += 1 local palIdx = objPalIndex[mapVertIdx] local tintClr = clrPoints[palIdx] -- Find this mesh-vert's index on its corresponding poly: polyVertIdx = findItem polyVerts triVerts[triVertNum] local polyMapNum = polyMapVerts[polyVertIdx] if not vertsSet[polyMapNum] do ( polyOp.setMapVert tempObj tintChannel polyMapNum tintClr vertsSet[polyMapNum] = true ) ) ) ) ) ) -- Paste edited channel back onto original object: local modName = if isFirstRow then defaultModName else tempModName RsVertClr_applyTempData tempObj thisObj tintChannel ignoreExistingMods:#(defaultModName) modName:modName ) if doSuspend do ( resumeEditing() -- Revert object/modifier selection: if (selPanel == undefined) then ( if ($ != selObj) do (select selObj) ) else ( modPanel.setCurrentObject selPanel node:selObj ui:true if (modPanel.getCurrentObject() == selPanel) do ( subObjectLevel = subObjWas ) ) ) dontAllowCallbacks = dontAllowCallbacksWas ) if RsMeshTintDebug do (format "%[/paintPaletteToMeshes]\n" debugTabs) ), -- Get colours used by current face-selection, if in a subobject-selection mode: fn getSelSubObjColours debugFuncSrc:"" debugTabs:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[getSelSubObjColours]<=[%]\n" debugTabs debugFuncSrc) dontAllowCallbacks = true local selMapVertClrs = #() -- Get current selection-panel state: local selPanel = modPanel.getCurrentObject() local selClass = classOf selPanel local selType = RsGetSubObjLevelName selClass:selClass -- Only collect colours in subobject-mode: if (selType == #object) do return selMapVertClrs -- Ensure that top of modifier-stack is shown: showEndResult = true local objOp = RsMeshPolyOp selObj -- Cancel if tintChannel is not active: if (objOp == undefined) or (not objOp.getMapSupport selObj tintChannel) do return selMapVertClrs -- Convert edge-selections to vert-selection: if (selType == #edge) do (selType = #vertex) -- Work with mesh-copy to get colour-data from modifiers too: local objMesh = copy selObj.mesh local vertArray, faceArray case selType of ( #vertex: ( -- Get faces using vert selection: vertArray = RsGetSelVertNums objMesh faceArray = meshOp.getFacesUsingVert objMesh vertArray ) #object: ( -- Get all faces: vertArray = #{1..objMesh.numVerts} faceArray = #{1..objMesh.numFaces} ) default: ( -- Get face selection: vertArray = #{1..objMesh.numVerts} faceArray = RsGetSelFaceNums objMesh ) ) local selMapVerts = #{} selMapVerts.count = meshOp.getNumMapVerts objMesh tintChannel -- Get colours used by face-verts: for faceNum in faceArray do ( -- Get face-verts for mesh/map: local meshFaceVerts = getFace objMesh faceNum local mapFaceVerts = meshOp.getMapFace objMesh tintChannel faceNum -- Get colours of verts corresponding to selection: for vertIdx = 1 to 3 do ( if vertArray[meshFaceVerts[vertIdx]] do ( local mapVert = mapFaceVerts[vertIdx] -- If mapvert hasn't been seen before, get its colour: if not selMapVerts[mapVert] do ( local mapVertPos = meshOp.getMapVert objMesh tintChannel mapVert mapVertPos *= 255 local vertClr = color (mapVertPos.x as integer) (mapVertPos.y as integer) (mapVertPos.z as integer) append selMapVertClrs vertClr ) selMapVerts[mapVert] = true ) ) ) selMapVertClrs = makeUniqueArray selMapVertClrs dontAllowCallbacks = false if RsMeshTintDebug do (format "%[/getSelSubObjColours]\n" debugTabs) return selMapVertClrs ), -- Compares the stored palette for the object along with a newly -- generated palette based on current colours for the object to -- make sure they match. It also then allows us to get the indexes -- for the colours in the palette which are used when swapping what -- row to paint onto the object. -- -- Needs to be order independent, we only care that the same colours exist in the first row, not the order fn comparePalettes coloursA coloursB debugFuncSrc:"" debugTabs:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[comparePalettes]<=[%]\n" debugTabs debugFuncSrc) local retVal = (coloursA.count == coloursB.count) for clr in coloursA while retVal do ( retVal = (findItem coloursB clr != 0) ) return retVal ), -- Return list of tint-materials, texmaps, and faces used per matId on object: fn getObjMatTexmaps thisObj debugFuncSrc:"" debugTabs:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[getObjMatTexmaps]<=[%] thisObj: %\n" debugTabs debugFuncSrc (thisObj as string)) -- Get object materials: local matFaces = #() local matIds = #() local objMats = RsGetMaterialsOnObjFaces thisObj faceLists:matFaces mtlIdList:matIds -- Get object material: local objMat = thisObj.material local objOp = RsMeshPolyOp thisObj local objGetFaceMatID = RsGetFaceMatIDFunc thisObj -- Find the texmaps for each of the materials: local matMapList = #() struct matIdTexMaps (matId, faces, material, texMapNums, path) for matIdx = 1 to objMats.count do ( local mat = objMats[matIdx] local matId = matIds[matIdx] local faces = matFaces[matIdx] -- Does material have a tint-shader? if (isKindOf mat Rage_Shader) and (matchPattern (RstGetShaderName mat) pattern:"*_tnt*") do ( local matVarCount = (RstGetVariableCount mat) -- Collect texmap var-nums: local texMaps = for v = 1 to matVarCount where (matchPattern (RstGetVariableType mat v) pattern:"texmap") collect v if (texMaps.count != 0) do ( append matMapList (matIdTexMaps matId:matId faces:faces material:mat texMapNums:texMaps) ) ) ) if RsMeshTintDebug do (format "%[/getObjMatTexmaps]\n" debugTabs) return matMapList ), -- Get object's current objPalIndex arrays by generating temp palettes: fn getPaletteIndexes newPalette:#() debugFuncSrc:"" debugTabs:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[getPaletteIndexes]<=[%] FileName: % coloursArray:%\n" debugTabs debugFuncSrc (tempPalFilename as string) (coloursArray as string)) local newObjPalIndexes = generatePalette temp:True debugFuncSrc:"getPaletteIndexes" debugTabs:debugTabs -- Get palettes-array for generated palette, appending it to supplied argument if used: local loadPals = (gRsMeshTintFuncs.loadPalettesFromFile tempPalFilename) if (loadPals.count != 0) do ( join newPalette loadPals[1] ) deletefile tempPalFilename if (paletteRows[1] != undefined) do ( if RsMeshTintDebug do (format "%* Reordering vert-indices to match current palette\n" debugTabs) -- Get index-changer lookup-list: local loadedPalette = paletteRows[1] local palCount = loadedPalette.count local clrMoveList = getOldNewLookupList newPalette loadedPalette debugFuncSrc:"getPaletteIndexes" debugTabs:debugTabs -- Reorder vert-index list to match currently-loaded palette: newObjPalIndexes = for palIndexes in newObjPalIndexes collect ( for oldIdx in palIndexes collect ( local newIdx = clrMoveList[oldIdx] if (newIdx == 0) do ( newIdx = if (oldIdx <= palCount) then oldIdx else 1 ) newIdx ) ) ) if RsMeshTintDebug do (format "%[/getPaletteIndexes]\n" debugTabs) return newObjPalIndexes ), -- reloads the user defined palette fn reloadPalette fromHD:False debugFuncSrc:"" debugTabs:"" = ( if (not isValidNode selObj) do return false if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[reloadPalette]<=[%] (obj: %)\n" debugTabs debugFuncSrc (obj as string)) palEdited = True ::RsMeshTintToolRoll.setSavedStatusClr() -- Remove non-default-row palette-pastes: removeTempModifiers debugFuncSrc:"reloadPalette" debugTabs:debugTabs -- Load from hd-model's palette, if applicable: local loadPalFilename = if fromHD then hdPalFilename else palFilename local palettesFromBmp = gRsMeshTintFuncs.loadPalettesFromFile loadPalFilename paletteRows = palettesFromBmp palEdited = (if fromHD then True else False) local defaultPal = palettesFromBmp[1] if fromHD do ( local lodPals = gRsMeshTintFuncs.loadPalettesFromFile palFilename palMatches = comparePalettes lodPals[1] defaultPal debugFuncSrc:"reloadPalette:fromHD" debugTabs:debugTabs -- Show if palette needs to be regenerated: if (not palMatches) do (::RsMeshTintToolRoll.setPalUnmatched()) ) -- Trim default-coloured (i.e. unedited) palettes from end of list: ( local doRemove = true local setPalCount = palettesFromBmp.count for n = palettesFromBmp.count to 2 by -1 while doRemove do ( doRemove = comparePalettes defaultPal palettesFromBmp[n] debugFuncSrc:"reloadPalette" debugTabs:debugTabs if doRemove do (setPalCount -= 1) ) palettesFromBmp.count = setPalCount ) -- Set palette-indices for current version of objects, changing indices to match latest palettes: objPalIndexes = getPaletteIndexes() ::RsMeshTintToolRoll.regenerateMeshTintingUI debugFuncSrc:"reloadPalette" debugTabs:debugTabs -- Paint loaded palette to mesh: local retVal = paintPaletteToMeshes debugFuncSrc:"reloadPalette" if RsMeshTintDebug do (format "%[/reloadPalette]\n" debugTabs) return retVal ), fn matchToOldPalette oldColoursArray &newColoursArray moveVertIndexes:true debugFuncSrc:"" debugTabs:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[matchToOldPalette]<=[%]\n\t%oldColoursArray: %\n\t%newColoursArray: %\n" debugTabs debugFuncSrc debugTabs (oldColoursArray as string) debugTabs (newColoursArray as string)) -- Generate look-up table of which new colour-indexes the old colours need to be reassigned to: local clrMoveList = getOldNewLookupList newColoursArray[1] oldColoursArray[1] debugFuncSrc:"getPaletteIndexes" debugTabs:debugTabs -- Expand new-palettes list if there were more old-palettes: if (oldColoursArray.count > newColoursArray.count) do ( for n = (newColoursArray.count + 1) to (oldColoursArray.count) do ( append newColoursArray (deepCopy newColoursArray[1]) ) ) -- Copy old colours to matching indices in new palettes: for palNum = 2 to oldColoursArray.count do ( for newIdx = 1 to clrMoveList.count do ( local oldIdx = clrMoveList[newIdx] local setClr = if (newIdx == 0) then black else (copy oldColoursArray[palNum][oldIdx]) newColoursArray[palNum][newIdx] = setClr ) ) if RsMeshTintDebug do (format "%[/matchToOldPalette]\n" debugTabs) ), -- Regenerate default palette, remapping palettes colours onto those colours: fn regenUserPalette fromOriginal:True debugTabs:"" debugFuncSrc:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[regenUserPalette]<=[%] (palette: %)\n" debugTabs debugFuncSrc (palette as string)) -- Don't use face-data loaded on init if default palette has been changed: if (not palMatches) do ( fromOriginal = False ) if (isValidNode selObj) then ( -- Remove temp modifiers, we don't want to include those colours in the mix: removeTempModifiers debugFuncSrc:"regenUserPalette" debugTabs:debugTabs -- loadPalettesFromFile returns #() if palette doesn't exist yet: local oldColoursArray = paletteRows --gRsMeshTintFuncs.loadPalettesFromFile palFilename debugFuncSrc:"regenUserPalette" debugTabs:debugTabs -- Regenerate a new palette from the object -- Note: This will just be the top row that is created -- by the plugin, to get the current colours on the object local newObjPalIndexes = if fromOriginal then origObjPalIndexes else ( generatePalette temp:True debugFuncSrc:"regenUserPalette" debugTabs:debugTabs ) if (newObjPalIndexes == false) do return false -- Load in new colours: local newColoursArray = if fromOriginal then #(origDefaultPalette) else ( local newPal = gRsMeshTintFuncs.loadPalettesFromFile tempPalFilename debugFuncSrc:"regenUserPalette" debugTabs:debugTabs deleteFile tempPalFilename newPal ) if (oldColoursArray.count != 0) do ( -- This goes through and matches old colours to the new palette if there was more than 1 row in the palette before regeneration: matchToOldPalette oldColoursArray &newColoursArray debugFuncSrc:"regenUserPalette" debugTabs:debugTabs ) paletteRows = newColoursArray objPalIndexes = newObjPalIndexes palEdited = True -- Palette has been changed, needs saving palMatches = True -- Main palette matches generated version ::RsMeshTintToolRoll.regenerateMeshTintingUI debugFuncSrc:"regenUserPalette" debugTabs:debugTabs paintPaletteToMeshes debugFuncSrc:"regenUserPalette" debugTabs:debugTabs ::RsMeshTintToolRoll.bmpRegenOutlineClr.visible = True redrawViews() ) else ( MessageBox "You must have a valid mesh/poly object selected to regenerate its palette." title:"Error: Invalid Selection" ) if RsMeshTintDebug do (format "%[/regenUserPalette]\n" debugTabs) return true ), -- --fn: --desc: Generate vertex colours in the tint channel using texture-colours applied to faces -- --We have to colour by face as the number of texture verts with which to find the colour in the texture will probably differ --from the numbr of tint channel mapverts -- fn genPaletteFromTexture debugFuncSrc:"" debugTabs:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[genPaletteFromTexture]<=[%] obj:%\n" debugTabs debugFuncSrc (obj as string)) if (not isValidNode selObj) do return false dontAllowCallbacks = true local uvChannel = 1 -- Process each mesh in tintedObjs: for tintObj in tintedObjs do ( -- Sample textures at average positions of face-verts to build palette -- local tempObj = RsVertClr_makeTempObj tintObj copyChan:tintChannel -- Get diffuse texturemaps and their faces: local texMapItems = for item in (getObjMatTexmaps tempObj) collect ( local diffPath local mat = item.material for v = item.texMapNums while (diffPath == undefined) do ( if (matchPattern (RstGetVariableName mat v) pattern:"*diffuse*") do ( diffPath = RstGetVariable mat v ) ) if (diffPath == undefined) then dontCollect else ( item.path = diffPath item ) ) local objOp = RsMeshPolyOp tempObj local objGetMapFace = RsGetMapFaceFunc tempObj -- Set temp-object's tint-channel to default white: objOp.SetFaceColor tempObj tintChannel #all white local faceCount = tempObj.numFaces local uniqueColours = #() local colourFaces = #() for item in texMapItems do ( local matTexmap = openBitmap item.path local texmapWidth = matTexmap.width local texmapHeight = matTexmap.height local bmpSize = [texmapWidth, texmapHeight] for f = item.faces do ( local mapVerts = objGetMapFace tempObj uvChannel f -- Find average vert-position for face: local UVpos = [0,0,0] for vert in mapVerts collect ( UVpos += objOp.getMapVert tempObj uvChannel vert ) UVpos /= (mapVerts.count as float) -- Convert UV-coords to texture-coords: local bmpPos = bmpSize * UVpos bmpPos.Y = (bmpSize.Y - bmpPos.Y) -- Make sure coordinates are within bitmap's coord-boundaries: for n = 1 to 2 do ( local val = mod (integer bmpPos[n]) bmpSize[n] if (val < 0) do (val += bmpSize[n]) bmpPos[n] = val ) -- Lookup colour in texture: local theColour = (getPixels matTexmap bmpPos 1)[1] -- Collect unique colours, and the faces that use them: local colourNum = findItem uniqueColours theColour if (colourNum == 0) do ( append uniqueColours theColour colourNum = uniqueColours.count append colourFaces #{} colourFaces[colourNum].count = faceCount ) colourFaces[colourNum][f] = true ) ) -- Apply colours to object-faces, setting per colour: for colourIdx = 1 to uniqueColours.count do ( objOp.SetFaceColor tempObj tintChannel colourFaces[colourIdx] uniqueColours[colourIdx] ) -- Transfer data from temp-object (replacing existing default-palette modifier, if found) RsVertClr_applyTempData tempObj tintObj tintChannel modName:defaultModName ) regenUserPalette fromOriginal:False debugFuncSrc:"genPaletteFromTexture" debugTabs:debugTabs dontAllowCallbacks = false if RsMeshTintDebug do (format "%[/genPaletteFromTexture]\n" debugTabs) ), -- Paint specific colour-index to subobject-selection: fn paintColourToSelection clrIdx debugFuncSrc:"" debugTabs:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[paintColourToSelection]<=[%] clrIdx:%\n" debugTabs debugFuncSrc (clrIdx as string)) dontAllowCallbacks = true -- Get set-colour from default palette-row: local selClr = paletteRows[1][clrIdx] -- Remember states affecting accessibility of subobject-selection: local selPanel = modPanel.getCurrentObject() local subObjWas = subObjectLevel local selType = RsGetSubObjLevelName selClass:(classOf selPanel) suspendEditing() -- Remove non-default-row palette-pastes: removeTempModifiers debugFuncSrc:"paintColourToSelection" debugTabs:debugTabs -- Apply main-palette colour: RsApplyVertColourChange chan:tintChannel action:#set inputVal:selClr modName:defaultModName selPanel:selPanel selType:selType -- Generate new palette-indexes: palMatches = False objPalIndexes = getPaletteIndexes() -- Re-show original palette: if (selectedRow != 1) do ( paintPaletteToMeshes row:selectedRow debugFuncSrc:paintColourToSelection debugTabs:debugTabs doSuspend:False ) resumeEditing() -- Revert object/modifier selection: if (selPanel == undefined) then ( if ($ != selObj) do (select selObj) ) else ( modPanel.setCurrentObject selPanel node:selObj ui:true if (modPanel.getCurrentObject() == selPanel) do ( subObjectLevel = subObjWas ) ) forceCompleteRedraw() dontAllowCallbacks = false if RsMeshTintDebug do (format "%[/paintColourToSelection]\n" debugTabs) return OK ), fn resetButtons = ( ::RsMeshTintToolRoll.bmpRegenOutlineClr.visible = False ::RsMeshTintToolRoll.bmpSaveOutlineClr.visible = False -- Activate "Load HD Palette" button if appropriate: ::RsMeshTintToolRoll.btnLoadHDPalette.enabled = (hdPalFilename != undefined) and (doesFileExist hdPalFilename) ), -- Set up this struct-instance's data based on current selection: fn init debugFuncSrc:"" debugTabs:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[paletteData.init]<=[%]\n" debugTabs debugFuncSrc) dontAllowCallbacks = true if (selection.count != 1) do ( resetButtons() if (selection.count > 1) do ( MessageBox "Select just one object before trying to load/regenerate" title:"Error: Too many objects selected" clearselection() ) dontAllowCallbacks = false return false ) ::RsMeshTintToolRoll.title = toolName -- Get initial object-list data: local gotObjs = getObjData() -- Abort if object-listing failed: if (not gotObjs) do ( messageBox "Select a Gta Object" title:"Error: Non-'Gta Object' selected" clearSelection() dontAllowCallbacks = false return false ) -- Set buttons to match new data: resetButtons() -- Show prop-name in window-title: ::RsMeshTintToolRoll.title += (" : " + (filenameFromPath palFilename)) if (getattrclass selObj != "Gta Object") do ( selobj = rootObj select selobj ) -- Set objs to show tint-channels: tintedObjs.showVertexColors = true tintedObjs.vertexColorType = #map_channel tintedObjs.vertexColorMapChannel = tintChannel -- Make sure there is tint colour info on selected-object's tintChannel: if (meshop.getMapSupport selObj.mesh tintChannel) then ( local newObjPalIndexes = #() local doCompare = true local validPalette = true -- If no palette for this object we generate one and get the -- colour indexes from that directly, otherwise we create another -- palette based on the colours currently on the object and compare -- it to what we have stored already (allowing us to get the indexes too). if (not RsFileExist palFilename) do ( format "NO PALETTE IMAGE - REGENERATING\n" local genVal = generatePalette temp:False debugFuncSrc:"init" debugTabs:debugTabs -- Cancel out if palette-load failed: if (genVal == false) do ( dontAllowCallbacks = false return false ) doCompare = false ) --Load the palette image local coloursArray = gRsMeshTintFuncs.loadPalettesFromFile palFilename debugFuncSrc:"init" debugTabs:debugTabs -- Only do the palette comparison if there was a saved palette already if doCompare and (coloursArray.count != 0) do ( -- Generate palette-data for current version of model: local newPalette = #() getPaletteIndexes newPalette:newPalette debugFuncSrc:"init" debugTabs:debugTabs validPalette = comparePalettes coloursArray[1] newPalette debugFuncSrc:"init" debugTabs:debugTabs palMatches = validPalette if not validPalette do ( RsMeshTintToolRoll.setPalUnmatched() if (RsMeshTintToolRoll.autosaveTimer != undefined) then RsMeshTintToolRoll.autosaveTimer.Stop() local texturePath = substring palFilename ((RsConfigGetTextureSourceDir()).count + 1) -1 local msg = stringStream "" format "%'s mesh-tint channel doesn't match file:\n\n%\n\nDo you want to regenerate default palette?" propName texturePath to:msg local buttonLabels = #("From Object", "From File", "Cancel") local toolTips = #(::RsMeshTintToolRoll.btnRegenPalette.tooltip, ::RsMeshTintToolRoll.btnReloadPalette.tooltip) if (hdPalFilename != undefined) and (doesFileExist hdPalFilename) do ( insertItem "From HD-object's File" buttonLabels 3 insertItem ::RsMeshTintToolRoll.btnLoadHDPalette.tooltip toolTips 3 ) local updateOption = RsQueryBoxMultiBtn (msg as string) title:"MeshTint Palette Update" timeout:-1 defaultBtn:2 labels:buttonLabels tooltips:toolTips -- Options for case updateOption of ( -- From Object: 1:(regenUserPalette debugFuncSrc:"init" debugTabs:debugTabs) -- From File: 2:(reloadPalette debugFuncSrc:"init" debugTabs:debugTabs) -- From HD File: 3:(reloadPalette fromHD:True debugFuncSrc:"init" debugTabs:debugTabs) ) if (RsMeshTintToolRoll.autosaveTimer != undefined) then ( RsMeshTintToolRoll.autosaveTimer.Start() RsMeshTintToolRoll.OnTick() ) ) ) if (validPalette) do ( reloadPalette debugFuncSrc:"init" debugTabs:debugTabs ) ) else ( local queryMsg = stringStream "" format "% does not have any tint information in channel %.\n\nShall I generate a new default palette from its diffuse textures?" propName tintChannel to:queryMsg if (queryBox (queryMsg as string) title:"Invalid Tint Mesh") then ( genPaletteFromTexture debugFuncSrc:"init" debugTabs:debugTabs ) else ( dontAllowCallbacks = false -- Allow triggered selection-callback to clear UI: clearselection() ) ) -- Turn selection-callbacks back on: dontAllowCallbacks = false if RsMeshTintDebug do (format "%[/paletteData.init]\n" debugTabs) return true ) ) rollout RsMeshTintToolRoll "Mesh Tinting Tool" width:1130 height:370 ( local thisRoll = RsMeshTintToolRoll local paletteData = paletteStruct() local colourClipboard -- Value used to copy/paste colours local handler_subObjSelChange = undefined --nodeevent var local minSize = [820, 224] local maxSize = [1800, 800] --autosave functions local autosaveDirectory = gRsMeshTintFuncs.userDefPalsPath + "autosave/" local autosaveTimer local autosavePath = "" local autosaveObj = undefined -- Maximum number of tint-palettes per object: local maxPalCount = 256 -- Acceptable initial palette-sizes: local paletteSizes = #(8, 16, 32, 64, 128, 256) local palDefaultColour = ( color 128 128 128 ) local use_UserDefPalette = false local comboMeshTextures = undefined local meshDiffuseDict = #() ------------- local wingDingFont = dotNetObject "System.Drawing.Font" "Wingdings" 18 local dnColour = dotNetClass "System.Drawing.Color" local dnKnownColour = dotNetClass "System.Drawing.KnownColor" local dnSelColour = dnColour.fromKnownColor dnKnownColour.MenuHighlight local textColour = (colorMan.getColor #text) * 255 local dnTextColour = dnColour.FromArgb textColour[1] textColour[2] textColour[3] local backColour = (colorMan.getColor #background) * 255 local dnBackColour = dnColour.FromArgb backColour[1] backColour[2] backColour[3] ------------- ------------- dotNetControl rsBannerPanel "panel" pos:[0,0] width:RsMeshTintToolRoll.width height:32 local banner = makeRsBanner dn_Panel:rsBannerPanel wiki:"Mesh_tinting_tool" versionNum:3.06 versionName:"Parallel Aftermath" groupBox grpBoxRegen "" align:#left width:143 height:60 offset:[-4,3] bitmap bmpRegenOutlineClr "" visible:false width:(141) height:(26) pos:(grpBoxRegen.pos + [1,7]) button btnRegenPalette "Regenerate Palette" height:20 width:135 pos:(bmpRegenOutlineClr.pos + [3,3]) tooltip:"Generate default palette from existing tint-channel\n\nColour-count is reduced to [Palette Size] if necessary" label lblPalSize "Palette Size:" across:2 align:#left pos:(btnRegenPalette.pos + [4,28]) tooltip:"Maximum number of colours in generated palettes" dropdownList comboPalSize "" width:60 pos:(btnRegenPalette.pos + [75,24]) local btnWidth = 100 local btnHeight = 30 button btnReloadPalette "Reload Palette" across:7 width:btnWidth height:btnHeight pos:(grpBoxRegen.pos + [grpBoxRegen.width + 12, 16]) tooltip:"Load selected object's tint-palettes from file" button btnLoadHDPalette "Load HD Palette" enabled:False width:btnWidth height:btnHeight pos:(btnReloadPalette.pos + [btnWidth + 5, 0]) tooltip:"Load selected lod's HD-model's tint-palettes from file" bitmap bmpSaveOutlineClr "" visible:false width:(btnWidth + 6) height:(btnHeight + 6) pos:(btnLoadHDPalette.pos + [btnWidth + 5, -3]) button btnSavePalette "Save Palette" width:btnWidth height:btnHeight pos:(bmpSaveOutlineClr.pos + [3, 3]) enabled:False tooltip:"Save tint-palettes to file" button btnGenPalByDiffuse "Diffuse => Palette" width:btnWidth height:btnHeight pos:(btnSavePalette.pos + [btnWidth + 8, 0]) tooltip:"Generate default tint-palette from object's diffuse textures.\n(Colours are sampled from face-midpoints)" dotNetControl paletteGridCtrl "RsCustomDataGridView" offset:[0,12] local defaultPalCol, lastPalCol, clickedCell, clickedCellType, addPalBtnRow local palSelColNum, palRndColNum, palKillColNum, lastPalColNum -- Palette-command button-labels & tooltips: local palBtnLabels = #("Select", "Random", "Remove") local palBtnTooltips = #("Show mesh-tint with this palette-id", "Randomise this palette's colours", "Remove this palette", "Change colour - rightclick for more commands") local palBtnTooltipTopExtra = #("\n\n(default palette)", "\n\n(top palette colours are kept unique)", undefined, "\n\n(top-palette colours will be shifted slightly to be non-unique)") ------------- fn setPalUnmatched = ( paletteData.palMatches = False bmpRegenOutlineClr.visible = True ) fn setSavedStatusClr setPalChanged: setPalMatches: = ( if (setPalChanged != unsupplied) do ( paletteData.palEdited = setPalChanged ) if (setPalMatches != unsupplied) do ( paletteData.palMatches = setPalMatches ) if (not paletteData.palMatches) do ( bmpRegenOutlineClr.visible = True ) local palRegenClr = if paletteData.palMatches then Green else Red bmpRegenOutlineClr.bitmap = bitmap bmpRegenOutlineClr.width bmpRegenOutlineClr.height color:palRegenClr btnRegenPalette.enabled = True -- Make sure that button draws on top of colour if paletteData.palEdited do ( bmpSaveOutlineClr.visible = True ) local palSavedClr = if paletteData.palEdited then Red else Green bmpSaveOutlineClr.bitmap = bitmap bmpSaveOutlineClr.width bmpSaveOutlineClr.height color:palSavedClr btnSavePalette.enabled = True -- Make sure that button draws on top of colour ) fn OnTick = ( -- try ( if not (isValidNode paletteData.selObj) then ( --print "No object selected" ) else ( local palFilename = paletteData.palFilename local autosaveFilename = autosaveDirectory + (filenameFromPath palFilename) --is current object same as last saved? if (paletteData.selObj == RsMeshTintToolRoll.autosaveObj) then ( -- autosave current file paletteData.savePalettes filename:autosaveFilename temp:true debugFuncSrc:"autosave" ) else ( --current object have a backup file? if (doesFileExist autosaveFilename) then ( RsMeshTintToolRoll.autosaveTimer.Stop() --if we have a backup file, but this is a newly selected object, then user can recover from this file local restoreFromBackup = queryBox "Backup palette found. Restore from backup?" title:"Mesh Tint Tool" if (restoreFromBackup) then ( deleteFile palFilename copyFile autosaveFilename palFilename paletteData.reloadPalette debugFuncSrc:"autosaveRestore" paletteData.paintPaletteToMeshes debugFuncSrc:"autosaveRestore" ) else ( --object has changed, delete the last backup palette if (doesFileExist RsMeshTintToolRoll.autosavePath) then deleteFile RsMeshTintToolRoll.autosavePath -- autosave current file paletteData.savePalettes filename:autosaveFilename temp:true debugFuncSrc:"autosave" ) RsMeshTintToolRoll.autosaveTimer.Start() ) else ( --object has changed, delete the last backup palette if (doesFileExist RsMeshTintToolRoll.autosavePath) then deleteFile RsMeshTintToolRoll.autosavePath -- autosave current file paletteData.savePalettes filename:autosaveFilename temp:true debugFuncSrc:"autosave" ) ) --note last saved object RsMeshTintToolRoll.autosaveObj = paletteData.selObj RsMeshTintToolRoll.autosavePath = autosaveFilename ) ) -- catch (print "Error: autosave failed") ) -- Deletes all rows of the palette. fn deleteAllRows = ( paletteGridCtrl.rows.clear() -- Remove all non-button columns: paletteGridCtrl.ColumnCount = palBtnLabels.count ) fn setPanelPaletteSize palSize = ( deleteAllRows() -- Create the colour-columns: for n = 1 to palSize do ( paletteGridCtrl.columns.add (defaultPalCol.clone()) ) -- Add panel-filling last column: lastPalColNum = paletteGridCtrl.columns.add (lastPalCol.clone()) paletteGridCtrl.ClearSelection() ) -- Add new row(s) to the datagrid, and colour the cells based on the colourArray fn addPaletteRows paletteRows defaultRow: debugFuncSrc:"" debugTabs:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[addPaletteRows]<=[%]\n" debugTabs debugFuncSrc) if (defaultRow == unsupplied) do ( defaultRow = paletteRows[1] ) for paletteRow in paletteRows do ( -- Add row, setting text for buttons: local rowID = paletteGridCtrl.Rows.Add palBtnLabels if RsMeshTintDebug do (format "%\t[rowID:%] %\n" debugTabs rowID (paletteRow as string)) local theRowCells = paletteGridCtrl.Rows.Item[rowID].cells local cellTooltips = deepCopy palBtnTooltips if (rowID == 0) then ( -- Hide top-row kill-button: local cellBtn = theRowCells.item[palKillColNum] cellBtn.Value = "" -- Tweak top-row tooltip: for n = 1 to cellTooltips.count do ( if (palBtnTooltipTopExtra[n] == undefined) then ( cellTooltips[n] = "" ) else ( append cellTooltips[n] palBtnTooltipTopExtra[n] ) ) ) else ( -- Set the rest of the kill-cells to red: --theRowCells.item[palKillColNum].Style.BackColor = dnColour.red ) -- Set row's tooltips and specialised labels: for colIdx = 0 to (theRowCells.count - 2) do ( local cell = theRowCells.item[colIdx] -- Set tooltip: local tooltipNum = (colIdx + 1) if (tooltipNum > cellTooltips.count) do (tooltipNum = cellTooltips.count) cell.toolTipText = cellTooltips[tooltipNum] case colIdx of ( palSelColNum: ( local newText = "ID:" + (rowID as string) if (rowID == 0) do ( newText = "[" + newText + "]" ) cell.value = newText ) ) ) local palColOffset = palBtnLabels.count - 1 -- Set palette-cell colours: -- (palette-indexes run from after command-button columns, to before dummy-column) for colIdx = palBtnLabels.count to (theRowCells.count - 2) do ( local palIdx = colIdx - palColOffset -- Transfer default-palette colours to undefined cells in other palettes: if paletteRow[palIdx] == undefined do ( paletteRow[palIdx] = defaultRow[palIdx] ) local palClr = paletteRow[palIdx] -- Set cell's colour: local cellStyle = dotnetObject "DataGridViewCellStyle" cellStyle.BackColor = RsColorToSysDrawColor palClr theRowCells.item[colIdx].Style = cellStyle ) ) -- Add row with Add button, if we are allowed more: if (paletteRows.count < maxPalCount) then ( addPalBtnRow = paletteGridCtrl.Rows.Add #() local rowCells = paletteGridCtrl.Rows.Item[addPalBtnRow].cells local cellStyle = dotnetObject "DataGridViewCellStyle" cellStyle.backColor = dnBackColour for n = 0 to (rowCells.count - 1) do ( rowCells.Item[n].style = cellStyle ) local newPalCell = rowCells.Item[palSelColNum] newPalCell.value = "New" newPalCell.toolTipText = "Add new palette" ) else ( addPalBtnRow = undefined ) if RsMeshTintDebug do (format "%[/addPaletteRows]\n" debugTabs) ) -- Set Select-button colours: fn setSelBtnColour selNum:paletteData.selectedRow = ( for thisRowIdx = 1 to paletteGridCtrl.rowCount do ( local theRow = paletteGridCtrl.rows.item[thisRowIdx - 1] local selBtnCell = theRow.cells.item[palSelColNum] local newStyle = dotnetObject "DataGridViewCellStyle" newStyle.BackColor = if (thisRowIdx == selNum) then dnSelColour else dnBackColour selBtnCell.Style = newStyle ) ) -- Updates palette ui swatch to mark cells containing colours used by subobject selection. -- Called by node event when subobject selection changes. fn markSelSubObjColours ev nd debugFuncSrc:"" debugTabs:"" = ( -- Skip if callbacks are off-duty: if paletteData.dontAllowCallbacks do return false if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[markSelSubObjColours]<=[%]\n" debugTabs debugFuncSrc) local obj = if (isKindOf nd node) then nd else (GetAnimByHandle nd[1]) -- Skip if object is not in watch-list: if (findItem paletteData.tintedObjs obj) == 0 do (return false) -- Ensure that callbacks won't be called: paletteData.dontAllowCallbacks = true -- Get colours on selected subobjects: local selMapVertClrs = paletteData.getSelSubObjColours() -- Palette-colours use these column-indexes on grid... local paletteColIdxs = #{palBtnLabels.count..(lastPalColNum - 1)} -- Mark palette-cells whose colours are on selected subobjects: for rowIdx = 0 to (paletteGridCtrl.rowCount - 1) do ( local theCells = paletteGridCtrl.rows.item[rowIdx].cells for colIdx = paletteColIdxs do ( local theCell = theCells.item[colIdx] local cellBackClr = theCell.style.backColor local cellClr = RsSysDrawColorToColor cellBackClr -- Mark selected-subobject cells with O local cellVal = if (findItem selMapVertClrs cellClr) == 0 then undefined else "O" if (theCell.value != cellVal) do (theCell.value = cellVal) ) ) -- Ensure that callbacks are turned back on: paletteData.dontAllowCallbacks = false if RsMeshTintDebug do (format "%[/markSelSubObjColours]\n" debugTabs) ) -- Fill datagridview with palette-data: fn populateRows debugFuncSrc:"" debugTabs:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[populateRows]<=[%]\n" debugTabs debugFuncSrc) local paletteRows = paletteData.paletteRows local palSize = if (paletteRows[1] == undefined) then 0 else paletteRows[1].count setPanelPaletteSize palSize --Create the rows: addPaletteRows paletteRows debugFuncSrc:"markSelSubObjColours" debugTabs:"" -- Set the Select-button colours: setSelBtnColour() -- Mark cells whose colours are used by current subobject selection: if (selection.count == 1) do ( markSelSubObjColours undefined selection[1] debugFuncSrc:"markSelSubObjColours" debugTabs:"" ) if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[/populateRows]\n" debugTabs) ) -- Populates the paletteGridCtrl with data from the object's palette file: fn regenerateMeshTintingUI debugFuncSrc:"" debugTabs:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[regenerateMeshTintingUI]<=[%]\n" debugTabs debugFuncSrc) paletteData.selectedRow = 1 populateRows debugFuncSrc:"regenerateMeshTintingUI" debugTabs:"" setSavedStatusClr() if RsMeshTintDebug do (format "%[/regenerateMeshTintingUI]\n" debugTabs) ) -- --fn: selectPaletteRow --desc: Selects a row of the palette to paint onto the object -- fn selectPaletteRow rowIdx debugFuncSrc:"" debugTabs:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[selectPaletteRow]<=[%] [rowIdx: %]\n" debugTabs debugFuncSrc rowIdx) paletteData.selectedRow = rowIdx setSelBtnColour selNum:rowIdx local retVal = paletteData.paintPaletteToMeshes row:rowIdx debugFuncSrc:"debugFuncSrc" debugTabs:"" if RsMeshTintDebug do (format "%[/selectPaletteRow]\n" debugTabs) return retVal ) -- --fn: addNewPaletteRow --desc: Adds a new palette row, restricted to the length of the first row -- fn addNewPaletteRow debugFuncSrc:"" debugTabs:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[addNewPaletteRow]<=[%]\n" debugTabs debugFuncSrc) -- Enforce palette-count limit: if (paletteData.paletteRows.count < maxPalCount) do ( -- Append a copy of the first palette row to the end of palette-list: local copyFirstRow = deepcopy paletteData.paletteRows[1] append paletteData.paletteRows copyFirstRow -- Remove last row from listview, the one one with the "New" button: if (addPalBtnRow != undefined) do ( paletteGridCtrl.Rows.Remove paletteGridCtrl.Rows.Item[addPalBtnRow] ) -- Add new palette-row to panel: addPaletteRows #(copyFirstRow) debugFuncSrc:"addNewPaletteRow" debugTabs:"" -- Enable "Save Palette" button, as changes have been made: setSavedStatusClr setPalChanged:True -- Select new palette: selectPaletteRow paletteData.paletteRows.count debugFuncSrc:"addNewPaletteRow" debugTabs:"" ) if RsMeshTintDebug do (format "%[/addNewPaletteRow]\n" debugTabs) ) -- --fn: eventSelectionChanged --desc: loads in the palette from the selected object, called when the -- selection changes -- fn eventSelectionChanged debugFuncSrc:"" debugTabs:"" = ( if RsMeshTintDebug do (debugTabs = debugTabs + " "; format "%[eventSelectionChanged]<=[%]\n" debugTabs debugFuncSrc) if paletteData.dontAllowCallbacks do ( if RsMeshTintDebug do (format "%[dontAllowCallbacks...]\n" debugTabs) return false ) paletteData.dontAllowCallbacks = true local rootObj = paletteData.rootObj local selObj = paletteData.selObj -- Trigger tidy-up if object-selection has changed: if (isValidNode selObj) and (selObj != $) and (isValidNode rootObj) do ( -- Ask if user wants to save tint-changes: if paletteData.palEdited do ( local filename = paletteData.palFilename local msg = stringStream "" format "Save changes made to %'s palette before deselecting?\n\n%" paletteData.propName filename to:msg local queryVal = RsQueryBoxMultiBtn (msg as string) title:"Warning: Deselecting object with edited palettes" timeout:-1 \ labels:#("Save Palette", "Don't Save", "Revert Selection") \ tooltips:#("Save palette changes to:\n" + filename, "Discard palette changes", "Undo Selection") case queryVal of ( 1: -- Save palette-changes: ( paletteData.savePalettes fileName:fileName debugFuncSrc:"eventSelectionChanged" debugTabs:debugTabs ) 3: -- Undo selection-change: ( select selObj -- Skip the rest of this event-function: paletteData.dontAllowCallbacks = false return OK ) ) ) -- Remove non-base tint-modifiers from current paletteData obj: paletteData.removeTempModifiers debugFuncSrc:"eventSelectionChanged" debugTabs:debugTabs ) -- Reset palette-data: paletteData = paletteStruct() setPanelPaletteSize 0 -- Initialise data to selection: bmpSaveOutlineClr.visible = False local success = paletteData.init debugFuncSrc:"eventSelectionChanged" debugTabs:"" btnSavePalette.enabled = success -- Ensure that selection-callback is back up: paletteData.dontAllowCallbacks = false if RsMeshTintDebug do (format "%[/eventSelectionChanged]\n" debugTabs) ) fn eventPreOpen = ( -- Clear tool data on file-open: paletteData = paletteStruct() ) -- Warn about unsaved palettes before saving scene: fn eventPreSave params = ( -- Don't allow AutoBackup to trigger this function: if (params[1] == 3) do return false if (isValidNode paletteData.rootObj) do ( -- Set palette-row to 1, to get rid of temp-tint pastes: selectPaletteRow 1 debugFuncSrc:"addNewPaletteRow" debugTabs:"" -- Ask if user wants to save tint-changes: case of ( (not paletteData.palMatches): ( local msg = stringStream "" format "%'s saved palette doesn't match up with mesh.\n\nRegenerate and save palette before saving scene?\n\n%" (paletteData.propName) (paletteData.palFilename) to:msg if (queryBox (msg as string) title:"Regenerate palette tints?") do ( paletteData.regenUserPalette debugFuncSrc:"eventPreSave|" paletteData.savePalettes debugFuncSrc:"eventPreSave|" ) ) (paletteData.palEdited): ( local msg = stringStream "" format "Save changes made to %'s palette before saving scene?\n\n%" (paletteData.propName) (paletteData.palFilename) to:msg if (queryBox (msg as string) title:"Save edited tints?") do ( paletteData.savePalettes debugFuncSrc:"eventPreSave|" ) ) ) ) ) -- Resize paletteGridCtrl and bannerPannel: fn arrangeCtrls = ( paletteGridCtrl.pos.x = 0 paletteGridCtrl.width = thisRoll.width paletteGridCtrl.height = thisRoll.height - paletteGridCtrl.pos.y rsBannerPanel.width = thisRoll.width ) -- Keep rollout size within max/min range: on RsMeshTintToolRoll resized newSize do ( case of ( (newSize.x < minSize.x):(thisRoll.width = minSize.x) (newSize.x > maxSize.x):(thisRoll.width = maxSize.x) ) case of ( (newSize.y < minSize.y):(thisRoll.height = minSize.y) (newSize.y > maxSize.y):(thisRoll.height = maxSize.y) ) arrangeCtrls() ) -- Don't allow rows to remain selected: on paletteGridCtrl SelectionChanged sender arg do ( if (sender.SelectedRows.count != 0) do ( paletteGridCtrl.ClearSelection() ) ) fn setPalColourTo palNum palClrNum newClr &changeMadeAll:True = ( -- These palettes will be edited: local setForPalNums = #(palNum) -- If first row, ensure that new colour is unique by using geometric method to find a colour that's close (but not too close to this or any existing colour) if (palNum == 1) do ( local mainPal = paletteData.paletteRows[1] -- Get other existing row-colours as point3's: local existingClrs = for clrNum = 1 to mainPal.count where (clrNum != palClrNum) collect ( mainPal[clrNum] as point3 ) -- Don't allow new colours that are closer than this rgb-distance: local minClrDist = 4 local triesPerSize = 500 local stopLoop = False local useColour = (newClr as point3) -- Used to find lighter/darker values: local lightAxisLength = 1.0 / (length [1,1,1]) local lightAxisOffset = [lightAxisLength, lightAxisLength, lightAxisLength] -- Expand sphere out until a suitable colour-point is found: for sphereGenDist = (minClrDist + 0.5) to 32.0 by 0.5 while (not stopLoop) do ( local tries = 0 local genTries = 0 for n = 1 to triesPerSize while (not stopLoop) do ( tries += 1 -- Colour is acceptable if it's not within minimum distance of any other: stopLoop = True for existingClr in existingClrs while (stopLoop) do ( stopLoop = ((distance useColour existingClr) >= minClrDist) ) if stopLoop then ( -- Integerise the channel-values: for n = 1 to 3 do ( useColour[n] = useColour[n] as integer ) newClr = useColour as color ) else ( -- Generate a random point on a sphere around our colour: local doSphereFind = True while doSphereFind do ( genTries += 1 local spherePos case genTries of ( -- Try up/down the lightness scale for the first two tries: 1: (spherePos = (sphereGenDist * lightAxisOffset)) 2: (spherePos = (-sphereGenDist * lightAxisOffset)) -- Generate random point on unit-sphere: Default: ( spherePos = (random [-1,-1,-1] [1,1,1]) ) ) local norm = sqrt (spherePos.x^2 + spherePos.y^2 + spherePos.z^2) spherePos /= norm useColour = (newClr as point3) + (sphereGenDist * spherePos) --format "%,%: %\n" sphereGenDist genTries useColour -- Try again if resulting colour-point was outside 0->255 colour-space: doSphereFind = False for n = 1 to 3 while (not doSphereFind) do ( local val = useColour[n] doSphereFind = not ((val >= 0) and (val <= 255)) ) ) ) ) ) if (paletteData.paletteRows.count > 1) and (queryBox "Change colour for all palettes?" title:"Mesh Tint") do ( changeMadeAll = True setForPalNums = (for n = 1 to paletteData.paletteRows.count collect n) ) ) for setPalNum in setForPalNums do ( paletteData.paletteRows[setPalNum][palClrNum] = newClr ) ) -- When a palette cell is clicked: on paletteGridCtrl CellMouseUp sender arg do ( --format "Clicked cell row: % column: % \n" arg.rowIndex arg.columnIndex --clear the selection paletteGridCtrl.clearSelection() -- Column-indexes for colour-cells: local paletteColIdxs = #{palBtnLabels.count..(lastPalColNum - 1)} local colIdx = arg.columnIndex local isFirstRow = (arg.rowIndex == 0) local isAddBtnRow = (arg.rowIndex == addPalBtnRow) local palNum = arg.rowIndex + 1 local paletteClrNum = arg.columnIndex - palBtnLabels.count + 1 -- What kind of control was clicked? clickedCellType = case of ( isAddBtnRow: ( case of ( (colIdx == palSelColNum):#add ) ) default: ( case of ( (colIdx == palSelColNum):#select ((colIdx == palKillColNum) and not isFirstRow):#remove (colIdx == palRndColNum):#random (paletteColIdxs[colIdx]):#palette Default:undefined ) ) ) -- Cancel if no valid selection was made: if (clickedCellType == undefined) do return false -- Rollout-value used to give info to rightclick menu: clickedCell = [paletteClrNum, palNum] local rightClick = arg.button.equals arg.button.right if rightClick then ( popUpMenu ::RSmenu_meshTintTool ) else ( local changeMade = False local changeMadeAll = False case clickedCellType of ( -- "New" clicked: #add: ( addNewPaletteRow() ) -- Select Palette button clicked: #select: ( -- Set the object's palette by the rowIndex: selectPaletteRow palNum debugFuncSrc:"addNewPaletteRow" debugTabs:"" ) -- Remove-palette button clicked: #remove: ( -- Delete this row: if queryBox "Are You sure you want to delete this palette row?" title:"Delete?" do ( paletteGridCtrl.rows.removeAt arg.rowIndex deleteItem paletteData.paletteRows palNum -- Set tool to default row if old one has been deleted: if (paletteData.selectedRow == palNum) do ( paletteData.selectedRow = palNum = 1 ) changeMade = true ) ) -- Random-palette button clicked: #random: ( local palSize = paletteData.paletteRows[palNum].count local newClrs = #() for n = 1 to paletteData.paletteRows[palNum].count do ( local newClr while (newClr == undefined) do ( -- Generate random colour (integered for searchability) newClr = random black white newClr = color (newClr.r as integer) (newClr.g as integer) (newClr.b as integer) -- Don't allow repeated colours in first row: if isFirstRow do ( if (findItem newClrs newClr) != 0 do ( newClr = undefined ) ) ) append newClrs newClr ) -- Replace palette with new version: paletteData.paletteRows[palNum] = newClrs changeMade = true ) -- Palette-cell clicked: #palette: ( local theCell = paletteGridCtrl.rows.item[arg.rowIndex].cells.item[arg.columnIndex] local cellClr = RsSysDrawColorToColor theCell.style.backColor local newColour = colorPickerDlg cellClr "" -- Stop if picker was cancelled, or colour hasn't changed: if (newColour != undefined) do ( if ( newColour == RsTintPaletteNullColour ) then ( MessageBox ("Colour invalid - it's used as the null colour" + (RsTintPaletteNullColour as string)) title:"Error: Null-colour selected" ) else ( setPalColourTo (arg.rowIndex + 1) paletteClrNum newColour changeMadeAll:&changeMadeAll changeMade = True ) ) ) ) if changeMade do ( -- Set setPalMatches to False if first palette has changed: local setPalMatches = if (changeMade and (changeMadeAll or (palNum == 1))) then False else unsupplied setSavedStatusClr setPalChanged:True setPalMatches:setPalMatches -- Redraw palettes: populateRows debugFuncSrc:"paletteGridCtrl clicked" debugTabs:"" -- If changed palette is selected, reapply it: if changeMadeAll or (paletteData.selectedRow == palNum) do ( RsMeshTintToolRoll.selectPaletteRow palNum debugFuncSrc:"addNewPaletteRow" debugTabs:"" ) ) ) ) -- Draws positions of verts with matching colour-index, when moused over: local drawPalIdx, drawRowClr, drawOutClr local doRedraw = True fn drawVertBlobs = ( if (not RsMeshTintToolRoll.open) do ( unregisterRedrawViewsCallback drawVertBlobs return False ) -- Do nothing if a non-palette cell is entered: if (drawPalIdx != undefined) and (paletteData.selObjNum != 0) do ( local selObj = paletteData.selObj local objMesh = copy selObj.mesh local objXform = selObj.objectTransform local objPalIndex = paletteData.objPalIndexes[paletteData.selObjNum] local vertsUsed = #{} vertsUsed.count = objMesh.numVerts local vertPositions = #() -- Get list of vert-positions with matching colour-index: local listVertNum = 0 for faceNum = 1 to objMesh.numFaces do ( for faceVertIdx = 1 to 3 do ( listVertNum += 1 if (objPalIndex[listVertNum] == drawPalIdx) do ( local meshVert = (getFace objMesh faceNum)[faceVertIdx] if not vertsUsed[meshVert] do ( vertsUsed[meshVert] = true local meshVertPos = getVert objMesh meshVert append vertPositions (meshVertPos * objXform) ) ) ) ) if (vertPositions.count != 0) do ( gw.setTransform (Matrix3 1) for vertPos in vertPositions do ( -- Draw filled circles at vert-points: local drawPos = gw.htranspoint vertPos + [1,1,0] gw.hRect (box2 [drawPos.x - 3, drawPos.y - 3] [drawPos.x + 4, drawPos.y + 3]) drawRowClr gw.hMarker drawPos #circle color:drawOutClr ) ) gw.enlargeUpdateRect #whole gw.updateScreen() ) ) on paletteGridCtrl CellMouseEnter sender arg do ( drawPalIdx = drawRowClr = drawOutClr = undefined if (paletteData.selObjNum != 0) do ( if (arg.rowIndex < paletteData.paletteRows.count) and (arg.ColumnIndex >= palBtnLabels.count) and (arg.ColumnIndex < lastPalColNum) and (isValidNode paletteData.rootObj) do ( drawPalIdx = (arg.ColumnIndex - palBtnLabels.count + 1) drawRowClr = paletteData.paletteRows[arg.rowIndex + 1][drawPalIdx] as point3 drawOutClr = if (distance drawRowClr [255,255,255] > 260) then white else black ) completeRedraw() ) ) on paletteGridCtrl CellMouseLeave sender arg do ( drawPalIdx = drawRowClr = drawOutClr = undefined if (paletteData.selObjNum != 0) do ( setNeedsRedraw complete:True ) ) on btnRegenPalette pressed do ( paletteData.regenUserPalette fromOriginal:False ) on btnReloadPalette pressed do ( paletteData.reloadPalette() ) on btnLoadHDPalette pressed do ( paletteData.reloadPalette fromHD:True ) on btnSavePalette pressed do ( setSavedStatusClr setPalChanged:True paletteData.savePalettes debugFuncSrc:"btnSavePalette" setSavedStatusClr setPalChanged:False ) on btnGenPalByDiffuse pressed do ( paletteData.genPaletteFromTexture() ) on comboPalSize selected num do ( gRsMeshTintFuncs.paletteWidth = paletteSizes[num] ) fn initpaletteGridCtrl = ( paletteGridCtrl.dock = paletteGridCtrl.dock.Fill paletteGridCtrl.AllowUserToAddRows = false paletteGridCtrl.AllowUserToDeleteRows = false paletteGridCtrl.AllowUserToOrderColumns = false paletteGridCtrl.AllowUserToResizeRows = false paletteGridCtrl.AllowUserToResizeColumns = false paletteGridCtrl.AutoSizeColumnsMode = paletteGridCtrl.AutoSizeColumnsMode.None paletteGridCtrl.multiSelect = false paletteGridCtrl.SelectionMode = paletteGridCtrl.SelectionMode.FullRowSelect paletteGridCtrl.readOnly = true -- Default cell/button colours: paletteGridCtrl.DefaultCellStyle.BackColor = dnBackColour paletteGridCtrl.DefaultCellStyle.ForeColor = dnTextColour -- Set up columns: paletteGridCtrl.ColumnHeadersVisible = false paletteGridCtrl.DefaultCellStyle.Alignment = (dotnetClass "DataGridViewContentAlignment").MiddleCenter paletteGridCtrl.RowTemplate.Height = 30 -- Set up default palette-colour column: defaultPalCol = dotNetObject "DataGridViewTextBoxColumn" defaultPalCol.width = 30 defaultPalCol.visible = true -- Set up default button-column: local buttonCol = dotNetObject "DataGridViewButtonColumn" buttonCol.FlatStyle = buttonCol.FlatStyle.Popup buttonCol.DefaultCellStyle.ForeColor = dnTextColour buttonCol.DefaultCellStyle.BackColor = dnBackColour buttonCol.AutoSizeMode = buttonCol.AutoSizeMode.AllCells buttonCol.MinimumWidth = 46 -- Define panel-filling dummy column: lastPalCol = defaultPalCol.clone() lastPalCol.AutoSizeMode = lastPalCol.AutoSizeMode.Fill -- Add select-column: palSelColNum = paletteGridCtrl.columns.add (buttonCol.clone()) -- Add random-palette column: palRndColNum = paletteGridCtrl.columns.add (buttonCol.clone()) --Add kill-column: palKillColNum = paletteGridCtrl.columns.add (buttonCol.clone()) ) on RsMeshTintToolRoll open do ( RsMeshTintToolRoll.title = paletteData.toolName -- Generate temp directories: RsMakeSurePathExists gRsMeshTintFuncs.tempPalsPath RsMakeSurePathExists autosaveDirectory -- Set up RS banner: banner.setup() -- Set panel-sizes to fit rollout: arrangeCtrls() -- Initialise the paletteGridCtrl control: initpaletteGridCtrl() -- Set the Palette Size combobox to the default palette-width size: comboPalSize.items = for item in paletteSizes collect (item as string) comboPalSize.selection = findItem paletteSizes gRsMeshTintFuncs.paletteWidth -- Make sure latest tint-palettes are synced: if (queryBox ("Sync to latest?\n\n" + gRsMeshTintFuncs.tintPalsPath) title:"Sync Tint Palettes") do ( gRsPerforce.syncChanged (gRsMeshTintFuncs.tintPalsPath + "...") clobberAsk:true silent:true progressTitle:"Syncing Tint Palettes..." ) -- Update rollout to show currently-selected object's palettes: eventSelectionChanged() -- Activate change-handlers: callbacks.addScript #selectionSetChanged "RsMeshTintToolRoll.eventSelectionChanged()" id:#meshTintTool callbacks.addScript #filePreSaveProcess "RsMeshTintToolRoll.eventPreSave (callbacks.notificationParam())" id:#meshTintTool callbacks.addScript #filePreOpen "RsMeshTintToolRoll.eventPreOpen()" id:#meshTintTool handler_subObjSelChange = NodeEventCallback mouseup:true subobjectSelectionChanged:markSelSubObjColours -- Start autosave timer autosaveTimer = dotnetobject "System.Windows.Forms.Timer" autosaveTimer.Interval = 5000 dotnet.addEventHandler autosaveTimer "Tick" OnTick autosaveTimer.Start() OnTick() registerRedrawViewsCallback drawVertBlobs ) on RsMeshTintToolRoll close do ( unregisterRedrawViewsCallback drawVertBlobs paletteData.dontAllowCallbacks = true callbacks.removeScripts id:#meshTintTool handler_subObjSelChange = undefined if (paletteData.tintedObjs.count != 0) do ( suspendEditing() -- Remove all UV-pastes from tint-channel: local tintChannel = gRsMeshTintFuncs.tintChannel for obj in paletteData.tintedObjs where isValidNode obj do ( for thisMod in obj.modifiers where (isKindOf thisMod UVW_Mapping_Paste) and (thisMod.mapId == tintChannel) do ( deleteModifier obj thisMod ) ) -- Paste default-tint modifier: paletteData.paintPaletteToMeshes debugFuncSrc:"tool close" doSuspend:False resumeEditing() -- Ask if user wants to save tint-changes: case of ( (not paletteData.palMatches): ( local msg = stringStream "" format "%'s saved palette doesn't match up with mesh.\n\nRegenerate and save palette before closing tool?\n\n%" (paletteData.propName) (paletteData.palFilename) to:msg if (queryBox (msg as string) title:"Regenerate palette tints?") do ( paletteData.regenUserPalette debugFuncSrc:"eventPreSave|" paletteData.savePalettes debugFuncSrc:"eventPreSave|" ) ) (paletteData.palEdited): ( local msg = stringStream "" format "Save changes made to %'s palette before closing tool?\n\n%" (paletteData.propName) (paletteData.palFilename) to:msg if (queryBox (msg as string) title:"Save edited tints?") do ( paletteData.savePalettes debugFuncSrc:"eventPreSave|" ) ) ) ) -- Clears handler_subObjSelChange node-callback: gc light:true --stop and dispose of timer if (autosaveTimer != undefined) do ( autosaveTimer.Stop() dotnet.removeAllEventHandlers autosaveTimer autosaveTimer.Dispose() ) --Remove autosave directory: local fileOps = dotNetClass "System.IO.Directory" if (doesFileExist autosaveDirectory) do ( fileOps.Delete autosaveDirectory true ) ) fn createRoll = ( try (destroyDialog RsMeshTintToolRoll) catch () -- Compile the custom dataGrid control if required: RS_CustomDataGrid() createDialog RsMeshTintToolRoll style:#(#style_titlebar, #style_resizing, #style_sysmenu, #style_minimizebox, #style_maximizebox) ) ) -- Rightclick menu: rcmenu RSmenu_meshTintTool ( -- These values are taken from parent rollout by menu-open event: local paletteData, palettes, palNum, palRow, palClrNum, isFirstRow -- Don't trigger menu for rightclicked non-palette cells: fn isPalClr = (RsMeshTintToolRoll.clickedCellType == #palette) fn canAdd = (isPalClr() and (palettes[1].count < 256)) fn canPaste = (isPalClr() and (isKindOf RsMeshTintToolRoll.colourClipboard color)) fn notFirstPal = (RsMeshTintToolRoll.clickedCell.y != 1) fn canMoveUp = (notFirstPal() and (palNum > 2)) fn canMoveDown = (notFirstPal() and (palNum < palettes.count)) fn canMoveSep = (isPalClr() and (canMoveUp() or canMoveDown())) menuItem itmAddClr "Add Colour" filter:canAdd menuItem itmCopyClr "Copy Colour" filter:isPalClr menuItem itmPasteClr "Paste Colour" filter:canPaste separator sepA filter:isPalClr menuItem itmApplyClr "Apply colour to selection" filter:isPalClr menuItem itmRandomClr "Randomise colour" filter:isPalClr separator sepC filter:canMoveSep menuItem itmMovePalUp "^ Move palette up" filter:canMoveUp menuItem itmMovePalDown "v Move palette down" filter:canMoveDown separator sepD menuItem itmDupePal "Duplicate Palette Row" menuItem itmDelPal "Delete Palette Row" filter:notFirstPal fn updateView reapply:True defaultChanged:True = ( -- Redraw palettes: RsMeshTintToolRoll.populateRows debugFuncSrc:"RSmenu_meshTintTool.updateView" debugTabs:"" -- If palette is selected, reapply it: if reapply and (paletteData.selectedRow == palNum) do ( RsMeshTintToolRoll.selectPaletteRow palNum debugFuncSrc:"RSmenu_meshTintTool.updateView" debugTabs:"" ) RsMeshTintToolRoll.setSavedStatusClr setPalChanged:True ) on itmAddClr picked do ( local addClr = colorPickerDlg palRow[palClrNum] "" if (addClr == undefined) do return False -- Insert new colour after rickclicked colour, in all palettes: for pal in palettes do ( insertItem addClr pal (palClrNum + 1) ) -- Shift the stored palette-indexes up to match the new palette: for objPalIndex in paletteData.objPalIndexes do ( for n = 1 to objPalIndex.count do ( if (objPalIndex[n] > palClrNum) do ( objPalIndex[n] += 1 ) ) ) RsMeshTintToolRoll.setPalUnmatched() updateView reapply:True defaultChanged:True ) on itmCopyClr picked do ( RsMeshTintToolRoll.colourClipboard = palRow[palClrNum] ) on itmPasteClr picked do ( -- Abort if not different: if (palRow[palClrNum] == RsMeshTintToolRoll.colourClipboard) do return false palRow[palClrNum] = RsMeshTintToolRoll.colourClipboard updateView reapply:True defaultChanged:isFirstRow ) on RSmenu_meshTintTool open do ( palClrNum = RsMeshTintToolRoll.clickedCell.x palNum = RsMeshTintToolRoll.clickedCell.y isFirstRow = (palNum == 1) paletteData = RsMeshTintToolRoll.paletteData palettes = paletteData.paletteRows palRow = palettes[palNum] if RsMeshTintDebug do ( format "Rightclick menu:\n" format " palNum: %\n" palNum format " palClrNum: %\n" palClrNum ) ) -- Apply rightclicked colour to selected faces: on itmApplyClr picked do ( paletteData.paintColourToSelection palClrNum ) -- Randomise selected colour: on itmRandomClr picked do ( local newClr while (newClr == undefined) do ( -- Generate integered random colour newClr = random black white newClr = color (newClr.r as integer) (newClr.g as integer) (newClr.b as integer) -- Don't allow repeated colours in first row: if isFirstRow do ( if (findItem palRow newClr) != 0 do ( newClr = undefined ) ) ) palRow[palClrNum] = newClr updateView reapply:True defaultChanged:isFirstRow ) -- Move selected palette up/down, or duplicate/delete it: fn movePalUpdown dir = ( if (dir != 0) do ( deleteItem palettes palNum ) if (dir != undefined) do ( insertItem palRow palettes (palNum + dir) ) local doReApply = False if (paletteData.selectedRow == palNum) do ( paletteData.selectedRow = case dir of ( 0:(palNum + 1) -- Duplicated undefined:(1) -- Deleted default:(palNum + dir) -- Moved ) doReApply = (dir == undefined) ) updateView reapply:doReApply defaultChanged:False ) on itmMovePalUp picked do ( movePalUpdown (-1) ) on itmMovePalDown picked do ( movePalUpdown (1) ) on itmDupePal picked do ( movePalUpdown (0) ) on itmDelPal picked do ( movePalUpdown (undefined) ) ) RsMeshTintToolRoll.createRoll()