-- 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()