Files
2025-09-29 00:52:08 +02:00

539 lines
18 KiB
Plaintext
Executable File

-- TPageBuilder_Zones.ms
-- Andy Davis - Rockstar London
-- February to March 2014
-- Cropping code for Texture Page Builder
-- filein this struct from TPageBuilder_UI.ms
----------------------------------------------------------------------------------------------------------------------------
--ZoneStruct
----------------------------------------------------------------------------------------------------------------------------
struct ZoneStruct
(
fn FormatObjectFaceList OFL =
(
for item in OFL do
(
format "%" (MaxOps.getNodeByHandle item.handle).Name
format "\t%\n" item.FaceList
)
),
fn GetUVZones ObjectFaceList &ZonesList =
(
for item in ObjectFaceList do
(
local handle = item.handle
local obj = MaxOps.GetNodeByHandle item.handle
local faceList = item.faceList
for faceID in faceList do
(
--get the UV bounds of this face
local vertIDs = (polyop.GetVertsUsingFace obj faceID) as array
local firstUV = RSPoly_GetSubVert_MapValue obj vertIDs[1] faceID 1
local minUV = [firstUV[1], firstUV[2]]
local maxUV = [firstUV[1], firstUV[2]]
numVerts = vertIDs.count
for id = 2 to numVerts do
(
local thisUV = RSPoly_GetSubVert_MapValue obj vertIDs[id] faceID 1
if thisUV[1] < minUV[1] then minUV[1] = thisUV[1]
if thisUV[2] < minUV[2] then minUV[2] = thisUV[2]
if thisUV[1] > maxUV[1] then maxUV[1] = thisUV[1]
if thisUV[2] > maxUV[2] then maxUV[2] = thisUV[2]
)
append ZonesList (UVZone (UVBounds minUV maxUV) (#(ObjectFaces handle #(faceID))))
)
)
),
--function tests whether two UVZone items intersect
--zone1, zone2 - the input zones to match
fn MatchZones zone1 zone2 fracture:false =
(
local MatchFound = false
--check if the U and V values match
if (fracture) then
(
if (zone1.UVBounds.UVMax[1] > zone2.UVBounds.UVMin[1]) then
(
if (zone1.UVBounds.UVMin[1] < zone2.UVBounds.UVMax[1]) then
(
if (zone1.UVBounds.UVMax[2] > zone2.UVBounds.UVMin[2]) then
(
if (zone1.UVBounds.UVMin[2] < zone2.UVBounds.UVMax[2]) then
MatchFound = true
)
)
)
)
else
(
if (zone1.UVBounds.UVMax[1] >= zone2.UVBounds.UVMin[1]) then
(
if (zone1.UVBounds.UVMin[1] <= zone2.UVBounds.UVMax[1]) then
(
if (zone1.UVBounds.UVMax[2] >= zone2.UVBounds.UVMin[2]) then
(
if (zone1.UVBounds.UVMin[2] <= zone2.UVBounds.UVMax[2]) then
MatchFound = true
)
)
)
)
MatchFound
),
-- ProcessUVZones creates temporary zone lists. this function manages the temporary list when a match is found
--&NewZones is a reference to the current NewZones array
--a and b are new additions to the NewZones array
fn AppendNewZones &NewZones a b =
(
NewZonesCount = NewZones.Count
if NewZonesCount > 0 then
(
local found = false
for i = 1 to NewZonesCount do
(
if (findItem NewZones[i] a > 0) or (findItem NewZones[i] b > 0) then
(
AppendIfUnique NewZones[i] a
AppendIfUnique NewZones[i] b
found = true
)
)
if not found then
(
append NewZones #(a, b)
)
)
else
NewZones = #(#(a, b))
),
--adds a new ObjectFaceList item to an array of ObjectFaceList items
fn AppendObjectFaceList &ObjectFaceList newItem =
(
local found = false
for item in ObjectFaceList do
(
if (item.Handle == newItem.Handle) then
(
item.FaceList = Sort (MakeUniqueArray (join item.FaceList newItem.FaceList))
found = true
)
)
if (found == false) then
append ObjectFaceList newItem
),
-- function joins a group of zones into one zone item
fn CondenseZones ZoneList idList =
(
local CondensedZone = ZoneList[idList[1]]
for id = 2 to idList.Count do
(
local ThisZone = ZoneList[idList[id]]
--extend the bounds
if (ThisZone.UVBounds.UVMin[1] < CondensedZone.UVBounds.UVMin[1]) then
CondensedZone.UVBounds.UVMin[1] = ThisZone.UVBounds.UVMin[1]
if (ThisZone.UVBounds.UVMin[2] < CondensedZone.UVBounds.UVMin[2]) then
CondensedZone.UVBounds.UVMin[2] = ThisZone.UVBounds.UVMin[2]
if (ThisZone.UVBounds.UVMax[1] > CondensedZone.UVBounds.UVMax[1]) then
CondensedZone.UVBounds.UVMax[1] = ThisZone.UVBounds.UVMax[1]
if (ThisZone.UVBounds.UVMax[2] > CondensedZone.UVBounds.UVMax[2]) then
CondensedZone.UVBounds.UVMax[2] = ThisZone.UVBounds.UVMax[2]
--append the ObjectFaceList
for item in ThisZone.ObjectFaceList do
(
AppendObjectFaceList &CondensedZone.ObjectFaceList item
)
)
CondensedZone
),
--ZonesList: list of object-faces, and their respective uv bounds
--returns UV zones
fn ProcessUVZones ZoneList =
(
local ContinueSort = true
local Iteration = 0 --used as a debug failsafe because of the while loop
while (ContinueSort and (Iteration < 100)) do
(
Iteration += 1 --failsafe for while loop
--collect an array of all the zones that should be grouped together into new zones
local ZoneListCount = ZoneList.count
local NewZoneGroups = #() --list of all the new groups of matched ZoneList items
local NewZoneIDs = #() --list of all the new matched ZoneList items
local UnchangedZoneIDs = #{} --list of the ids of zones in the ZoneList that have not been extended
local UnchangedZones = #() --buffer for these unchanged ZoneList items
local CondensedZones = #() --new zones created from condensing existing zones together
--first, get the ZoneList ID's of Zones that should be condensed
for i = 1 to (ZoneListCount - 1) do
(
for j = (i + 1) to ZoneListCount do
(
if (MatchZones ZoneList[i] ZoneList[j]) then
(
AppendNewZones &NewZoneGroups i j
AppendIfUnique NewZoneIDs i
AppendIfUnique NewZoneIDs j
)
)
)
--if we have not found any new zones, then the condensing process is complete
if NewZoneIDs.Count == 0 then
ContinueSort = false
else
(
--get a list of the ids for zones in the zone list that have not been condensed
UnchangedZoneIDs = (for i = 1 to ZoneListCount where (findItem NewZoneIDs i == 0) collect i)
for id in UnchangedZoneIDs do
append UnchangedZones ZoneList[id]
--create the new zones
for item in NewZoneGroups do
append CondensedZones (CondenseZones ZoneList item)
--rebuild the ZoneList from the new zones and the old ones
ZoneList = join UnchangedZones CondensedZones
)
)
if (iteration == 100) then
return "Error: UVZone iteration search limit exceeded"
ZoneList
),
--returns a rage folder with the given name and featuring the texturePath argument as the diffuse texture
--candidate for common functions
fn CreateRageDiffuseShader texturePath name tag:"Material" =
(
local rageShader = Rage_Shader name: (tag + " " + name)
local newBitmap = BitmapTexture name: ("Bitmap " + name) filename:texturePath
RstSetShaderName rageShader "default.sps"
RstSetVariable rageShader 1 newBitmap.filename
showTextureMap rageShader true
rageShader
),
--candidate for common functions
-- 1.7 million different random tags
fn RandomTag =
(
local alphabet = #("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z")
local tag = alphabet[random 1 26] + alphabet[random 1 26] + alphabet[random 1 26] + (random 10 99) as string
tag
),
--returns a list of all objects using a particular material
--candidate for common functions
fn GetObjectsUsingMaterial inMaterial =
(
local nodes = refs.dependents inMaterial
local objs = #()
for item in nodes where superClassOf item == GeometryClass do
if item.material == inMaterial then append objs item
objs
),
--function adds a material to an existing material resulting in an appended multimaterial
--returns the material id of the new material slot
--candidate for common functions
fn AppendMaterial baseMaterial newMaterial tag:"" =
(
local newMatID
if (classOf baseMaterial == MultiMaterial) then
(
local numsubs = baseMaterial.numsubs
baseMaterial.numsubs = numsubs + 1
baseMaterial[numsubs + 1] = newMaterial
newMatID = baseMaterial.materialIDList[numsubs + 1]
baseMaterial.Names[baseMaterial.materialIDList.count] = tag
)
else
(
local newMulti = MultiMaterial numsubs:2 name:(baseMaterial.name + " Multi")
local objs = GetObjectsUsingMaterial baseMaterial
newMulti[1] = baseMaterial
newMulti.MaterialList[2] = newMaterialX
newMulti.Names[2] = tag
objs.material = newMulti
addModifier objs (materialModifier materialid:1)
convertToPoly objs
newMatID = 2
)
newMatID
),
--function checks zones for zero area zones
--buffers them by the width of a pixel according to the input resolution
fn CheckZones &UVZones resolution =
(
local uNudge = 1.0 / resolution[1] --uv nudge distance equivalent to a pixel
local vNudge = 1.0 / resolution[2]
for item in UVZones do
(
if (item.UVBounds.UVmin[1] == item.UVBounds.UVmax[1]) then
(
if (item.UVBounds.UVmin[1] < 1) then
item.UVBounds.UVmax[1] = item.UVBounds.UVmin[1] + uNudge
else
item.UVBounds.UVmin[1] = item.UVBounds.UVmin[1] - uNudge
)
if (item.UVBounds.UVmin[2] == item.UVBounds.UVmax[2]) then
(
if (item.UVBounds.UVmin[2] < 1) then
item.UVBounds.UVmax[2] = item.UVBounds.UVmin[2] + vNudge
else
item.UVBounds.UVmin[2] = item.UVBounds.UVmin[2] - vNudge
)
)
),
--gets the id of the material list number from a materials matID value (for the case of missing materials)
fn GetMatListIDFromMatID multimat matid =
(
local matListID = 0
local numMaterialIDs = multimat.materialidlist.count
local found = false
for i = 1 to numMaterialIDs while not found do
(
if multimat.materialidlist[i] == matid then
(
matListID = i
found = true
)
)
matListID
),
--function applies one new multimaterial to a selection of editable poly objects
--candidate for common functions
--converts single materials into multimaterials
--polyobjects - array of objects to be merged
--if you send a single object in an array, the function applies an optimised multimaterial
--works without modifiers
--polyobjects: array of editable poly objects
--27/02/14: updated to respect name field
--13/03/14: updated to fix out of range matID's
--nb dependency on GetMatListIDFromMatID
fn MergeMultiMaterials polyobjects =
(
local MaterialList = #() --list of materials
local NameList = #()
--get all the materials into the MaterialList and set the material IDs
for item in polyobjects do
(
local thisMat = item.material
local faceCount = item.faces.count
if (classof item.material == MultiMaterial) then
(
for i = 1 to faceCount do
(
local matID = polyop.getFaceMatID item i
--check if matID legal: if not then fix it
if matID > item.material.count then
(
local correctedValue = mod matID item.material.count
if (correctedValue == 0) then
correctedValue = item.material.count
polyop.setFaceMatID item i correctedValue
matID = correctedValue
)
local mat = thisMat[matID]
local newMatID = findItem MaterialList mat
if (newMatID == 0) then
(
local matListID = this.GetMatListIDFromMatID thisMat matID --the number in meditMaterials list
append MaterialList mat
append NameList thisMat.Names[matListID]
newMatID = MaterialList.count
)
polyop.SetFaceMatID item i newMatID
)
)
else
(
local newMatID = findItem MaterialList thisMat
if (newMatID == 0) then
(
append MaterialList thisMat
append NameList ""
newMatID = MaterialList.count
)
for i = 1 to faceCount do
polyop.SetFaceMatID item i newMatID
)
)
--create the multimaterial
local multiName = "Multimaterial: "
if (polyobjects.count == 1) then
multiName += polyobjects[1].name
else
multiName += RandomTag()
local numMaterials = MaterialList.count
local newMultiMaterial = MultiMaterial numsubs:numMaterials name:multiName
for i = 1 to numMaterials do
(
newMultiMaterial[i] = MaterialList[i]
newMultiMaterial.Names[i] = NameList[i]
)
--apply the multimaterial
polyobjects.material = newMultiMaterial
),
fn SplitZones InputTD Apply:true =
--new smaller textures are created that match the UV-zones
--definition of UV zone: a collection of UV elements that fit within a shared bounding box
--InputTD: texture definition
--returns: texture definitions representing the zones generated from the InputTD, and a list of the new materials
--note that this function creates textures in the temporary folder, so not suitable for check in
--the reason for this is that the cropped textures are designed to be for temporary use only prior to texture page building
(
local ObjectFaceList = #()
local numObjs = InputTD.ObjectFaceList.Count
local UVZonesList = #() --master array containing all the UVZones and corresponding ObjectFace lists
local resolution = InputTD.GetResolution()
local CroppedMaterials = #()
local CroppedTextures = #()
local OutputTDs = #()
--get the ObjectFaceList for the current TextureDefinition
for i = 0 to (numObjs - 1) do
(
local handle = InputTD.ObjectFaceList.Item[i].Handle
local faceList = InputTD.ObjectFaceList.Item[i].FaceList.ToArray()
append ObjectFaceList (DataPair handle:handle faceList:faceList)
)
--get into the uv data and calculate the zones from the ObjectFaceList
this.GetUVZones ObjectFaceList &UVZonesList
--calculate the UV zones
UVZones = this.ProcessUVZones UVZonesList
this.CheckZones &UVZones resolution
-- TPageTool.FormatTextureDefinition InputTD --dev
--create the new cropped textures
if (UVZones.count == 1 and UVZones[1].UVBounds.uvmin == [0,0] and UVZones[1].UVBounds.uvmax == [1,1] ) then
OutputTDs = #(InputTD) --no cropping this texture
else
(
rollout temprollout "Texture Page Builder"
(
label myLabel "Cropping Textures"
)
theNewFloater = createDialog temprollout 200 30
TPageTool.TPageOps.StopRedraw()
for item in UVZones do
(
--create the new cropped texture
local umin = item.UVBounds.UVmin[1]
local vmin = item.UVBounds.UVmin[2]
local umax = item.UVBounds.UVmax[1]
local vmax = item.UVBounds.UVmax[2]
local info = InputTD.Path + "\n" + umin as string + "\n" + vmin as string + "\n" + umax as string + "\n" + vmax as string
-- messagebox info --dev
local newTexturePath = TPageTool.TPageOps.TPageBuilder.CropImage InputTD.Path umin vmin umax vmax
append CroppedTextures newTexturePath
if (Apply) then
(
local newShader = this.CreateRageDiffuseShader newTexturePath (RandomTag()) tag:"Cropped"
append CroppedMaterials newShader
--add the texture to the multimaterial
--all the objectFaces will use the same material, so just pick the first object
local firstObject = maxOps.getNodeByHandle (item.ObjectFaceList[1].handle)
local newMatID = this.AppendMaterial firstObject.material newShader tag:"cropped"
--loop throught the object faces, assigning the new texture and remapping the UV's
for objectFace in item.ObjectFaceList do
(
--select the faces
local obj = maxOps.getNodeByHandle objectFace.handle
local UVXFormMod = UVW_XForm()
--assign the texture
polyop.setFaceMatID obj (objectFace.faceList as bitarray) newMatID
--set the uv's for each face
--to transform the faces, we need to translate each face by -(umin, vmin) to set to origin
--then scale each face to expand to fill the cropped texture by (1/(umax - umin), 1/(vmax - vmin))
max modify mode --necessary for manual operations
select obj --must select the objects individually or the modifier gets added to all the objects
SubObjectLevel = 4
obj.editablepoly.SetSelection #Face #{} --clear current face selection
obj.editablepoly.SetSelection #Face (objectFace.faceList as bitarray) --select the faceList for this texture
--we have to add the UVW_XForm modifiers separately, as we can't control the transformation order
ModPanel.AddModToSelection (UVW_XForm U_Offset:-umin V_Offset:-vmin)
ModPanel.AddModToSelection (UVW_XForm U_Tile: (1/(umax - umin)) V_Tile: (1/(vmax - vmin)))
ConvertToPoly obj
SubObjectLevel = 0
)
TPageTool.TPageOps.ShowTextures true
)
--calculate new TextureDefinitions from the UVZones: each zone gets a new TD
append OutputTDs (TPageTool.CreateTextureDefinition newTexturePath item.ObjectFaceList)
)
TPageTool.TPageOps.ResumeRedraw()
destroyDialog temprollout
)
OutputTDs
)
)
----------------------------------------------------------------------------------------------------------------------------
--TEST
----------------------------------------------------------------------------------------------------------------------------
/*
ZoneOps = ZoneStruct()
polyobjs = for item in selection where SuperClassOf item == GeometryClass collect item
--assign a single material to all the affected objects
RSPoly_MergeMultiMaterials polyobjs
--build a TextureDefinitionList (TDL)
TPageOps = TPageStruct()
TPageOps.BuildTextureDefinitionList polyobjs
TPageOps.TPageBuilder.ProcessTextureDefinitionList() --adds texture resolutions and sorts
TDList = TPageOps.TPageBuilder.TextureDefinitionArray
clearListener()
--split each TD into zones
for item in TDList do
ZoneOps.SplitZones item
--optimise the multimaterials
for item in polyobjs do
RSPoly_MergeMultiMaterials #(item)
select polyobjs
*/