539 lines
18 KiB
Plaintext
Executable File
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
|
|
*/ |