2740 lines
84 KiB
Plaintext
Executable File
2740 lines
84 KiB
Plaintext
Executable File
-- 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 = #()
|
|
|
|
------------- <DOTNET VALUES>
|
|
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]
|
|
------------- </DOTNET VALUES>
|
|
|
|
------------- <CONTROLS>
|
|
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)")
|
|
|
|
------------- </CONTROLS>
|
|
|
|
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()
|