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

2710 lines
83 KiB
Plaintext
Executable File

-----------------------------------------------------------------------------
-- Combined-LOD Generator Tool (ComboLodder)
-----------------------------------------------------------------------------
-- Functions (loaded by lodCombinerTool_Data.ms)
-----------------------------------------------------------------------------
RSTA_LoadCommonFunction #("fn_RSTA_vertexColours.ms")
fileIn "pipeline/util/treelod_utils.ms"
fileIn "pipeline/helpers/materials/CreateMultiSub.ms"
filein (RsConfigGetWildWestDir() + "script/3dsMax/General_tools/Selection_Funcs.ms")
global idxIsComboLod = getattrindex "Gta Object" "Is ComboLod"
global idxLodDistance = getattrindex "Gta Object" "LOD distance"
global idxChildLodDistance = getattrindex "Gta Object" "Child LOD distance"
global idxInstLodDistance = getattrindex "Gta Object" "Instance LOD distance"
global idxPriority = getattrindex "Gta Object" "Priority"
global idxLowPriority = getattrindex "Gta Object" "Low Priority"
global idxTXD = getattrindex "Gta Object" "TXD"
global idxDontExport = GetAttrIndex "Gta Object" "Dont Export"
global idxDontAddToIpl = GetAttrIndex "Gta Object" "Dont Add To IPL"
global idxIPLGroup = getattrindex "Gta Object" "IPL Group"
global idxNewDlc = GetAttrIndex "Gta Object" "New DLC"
global idxTxdDlc = GetAttrIndex "Gta Object" "TXD DLC"
global idxEnableAmbientMultiplier = GetAttrIndex "Gta Object" "Enable Ambient Multiplier"
global idxAOMultiplier = GetAttrIndex "Gta Object" "AO Multiplier"
global idxArtificialAOMultiplier = GetAttrIndex "Gta Object" "Artificial AO Multiplier"
global idxTint = GetAttrIndex "Gta Object" "Tint"
global idxRemoveDegeneratePolys = GetAttrIndex "Gta Object" "Remove Degenerate Polys"
global idxSuperLodAlpha = GetAttrIndex "Gta Object" "Superlod with Alpha"
global idxUseFullMatrix = GetAttrIndex "Gta Object" "Use Full Matrix"
global idxGenerateWaterReflectiveLODs = GetAttrIndex "Gta Object" "Generate Water Reflective LODs"
global idxDontRenderInWaterReflections = GetAttrIndex "Gta Object" "Dont Render In Water Reflections"
global idxIgnoredByComboLodder = GetAttrIndex "Gta Object" "Ignored By ComboLodder"
struct RsComboLodTint
(
snapAccuracy = 0.2,
bmpSize = 64,
-- Stores data loaded from gRsLodCombinerVals.lodColourFile:
lodColourFile, pixelRows, maxBmpCoord, bmpHash,
searchList, colourPosList,
tintChan = 13,
fn loadBitmap =
(
lodColourFile = gRsLodCombinerVals.lodColourFile
-- Load scaled-down version of bitmap:
local loadBmp = openBitmap lodColourFile
local clrBmp = bitmap bmpSize bmpSize
renderMap (bitmapTexture bitmap:loadBmp preMultAlpha:False) filter:True into:clrBmp
close loadBmp
maxBmpCoord = (float (bmpSize - 1))
-- Load all colour-rows from bitmap:
pixelRows = for rowNum = 0 to (bmpSize - 1) collect
(
local thisRow = getPixels clrBmp [0,rowNum] bmpSize
-- Integerise colours, which will have been squished by resizer's filtering:
thisRow = for thisClr in thisRow collect (color (integer thisClr.r) (integer thisClr.g) (integer thisClr.b))
thisRow
)
close clrBmp
bmpHash = getHashValue pixelRows 1
return pixelRows
),
-- Snap colours to a courser grid (for faster searching of smaller palette)
fn snapColour clr =
(
return clr
/*
local newVal = copy clr
newVal *= snapAccuracy
newVal = (color (integer newVal.r) (integer newVal.g) (integer newVal.b)) / snapAccuracy
return newVal
*/
),
-- Convert to L*ab colour for decent perceptual colour-matching:
fn getSearchColourVal clr =
(
return (RsXYZtoCIELAB (RsRGBtoXYZ clr))
),
-- Collect colour/uv data from bitmap:
fn initData =
(
pushPrompt ("Analysing colour-map: " + (filenameFromPath gRsLodCombinerVals.lodColourFile))
local timeStart = timeStamp()
if (pixelRows == undefined) do loadBitmap()
local clrList = #()
local searchClrList = #()
local clrPosList = #()
-- Scan through bitmap, collecting colours/positions:
-- (starting with bottom, so grey pixels will map to there by preference)
-- IGNORE ROW 1, it contains colours that confuse matters
for rowNum = bmpSize to 2 by -1 do
(
local rowPixels = pixelRows[rowNum]
for colNum = 1 to bmpSize do
(
-- Convert colour to snapped version:
local snapClr = snapColour rowPixels[colNum]
local clrNum = findItem clrList snapClr
if (clrNum == 0) do
(
append clrList snapClr
append clrPosList #()
clrNum = clrList.count
)
append clrPosList[clrNum] [colNum, rowNum, 0]
)
)
-- Find suitable UV-position for each colour, and sort colours into tinted/grey lists:
(
local origClrPosList = for clr in clrList collect (clr as point3)
searchList = #()
colourPosList = #()
for idx = 1 to clrList.count do
(
local thisClr = clrList[idx]
-- Find average positions of same-colour pixels found within 4 pixels of first instance found
local posList = clrPosList[idx]
local firstPos = posList[1]
local posSum = copy firstPos
local posCount = 1
for n = 2 to posList.count do
(
if (distance firstPos posList[n]) < 4 do
(
posSum += posList[n]
posCount += 1
)
)
-- Collect average pixel-position, converted to UV-coords:
local getPos = (posSum / posCount)
-- Make sure value is in middle of a pixel:
getPos = [floor getPos.X, floor getPos.Y, 0] + [0.5, 0.5, 0.0]
-- Correct for Y/V direction difference:
getPos.Y = (bmpSize - getPos.Y)
getPos /= maxBmpCoord
local addColours = #(thisClr)
/*
-- Add variations on colour, to make it more likely that this shade will be chosen:
for mult = #(0.75, 1.25) do
(
local newClr = copy thisClr
newClr.value *= mult
newClr.saturation *= mult
newClr = snapColour newClr
-- Don't add extra-match colours if they're too close to any of the original colours:
if (findItem clrList newClr) == 0 do
(
local notNearClrs = True
local newClrPos = newClr as point3
for clrPos in origClrPosList while notNearClrs do
(
notNearClrs = ((distance clrPos newClrPos) > 4)
)
if notNearClrs do
(
append addColours newClr
)
)
)
*/
for clr in addColours do
(
append searchList (getSearchColourVal clr)
append colourPosList getPos
)
)
)
format "Lod Colour Init: %s\n" ((timeStamp() - timeStart) / 1000.0)
popPrompt()
return True
),
-- Find UVs for colours in LOD-prop bitmap that closest match colour-list:
fn getBmpUVsForColour convertClr =
(
-- Find transformed version of requested colour:
local searchClr = (getSearchColourVal convertClr)
-- Find index of closest colour in palette:
local searchDists = for clr in searchList collect (distance searchClr clr)
local findNum = findItem searchDists (amin searchDists)
return colourPosList[findNum]
),
-- Returns colours for a given UV-position:
fn getBmpColourForUVs uvPos =
(
-- Convert uv-position to bitmap-size position, clipped to 0-maxBmpCoord area:
local bmpPos = maxBmpCoord * uvPos
bmpPos.Y = (maxBmpCoord - bmpPos.Y)
for n = 1 to 2 do
(
local val = mod (integer bmpPos[n]) maxBmpCoord
if (val < 0) do (val += maxBmpCoord)
bmpPos[n] = val
)
-- Collect colour from bitmap-colours:
local retVal = pixelRows[bmpPos.y + 1][bmpPos.x + 1]
--format "UV2Colour: % -> % %\n" uvPos bmpPos retVal
return retVal
),
-- Returns lod-prop colours for each mapping-vert
fn getMeshUVcolours meshIn chan:1 =
(
local uvCount = meshOp.getNumMapVerts meshIn chan
local uvVerts = for n = 1 to uvCount collect
(
local uvVert = meshOp.getMapVert meshIn chan n
getBmpColourForUVs uvVert
)
),
fn applyLodTints slodRefs =
(
(
local logMsg = "[ApplyLodTints]"
gRsUlog.LogMessage logMsg context:"ComboLodder"
)
-- Apply meshtint uvs to refs:
RSrefFuncs.setTintMeshes refDefs:slodRefs
local lodTintPattern = gRsLodCombinerVals.lodTintPattern
for slodRef in slodRefs where (slodRef.tintMeshObjs != undefined) do
(
local slodMesh = slodRef.meshObj.mesh
-- Get materials used on mesh (and their faces)
local slodMats = #()
local facesPerMat = #()
RsGetMaterialsOnObjFaces slodMesh material:slodRef.material materials:slodMats faceLists:facesPerMat
local doFaces = #{}
-- Filter down to suitable materials - only apply tints to faces marked with prop_lod_TINTED.bmp:
(
local useMatNums = for n = 1 to slodMats.count do
(
local mat = slodMats[n]
if (isKindOf mat Rage_Shader) and (getNumSubTexmaps mat >= 1) do
(
local subTexMap = getSubTexmap mat 1
if (subTexMap != undefined) and (subTexMap.filename != "") and (matchPattern subTexMap.filename pattern:lodTintPattern) do
(
doFaces += (facesPerMat[n] as bitArray)
)
)
)
)
if (doFaces.numberSet == 0) then
(
-- Don't use tint-meshes if mesh has no suitable tintable materials:
slodRef.tintMeshObjs = undefined
)
else
(
-- Initialise colour-conversion data, if needed:
if (searchList == undefined) do (initData())
-- We're currently using a default grey tint for every face
/*
-- Get colours per mapvert: (taken from lod-colour bitmap)
local mapVertColours = for clr in getMeshUVcolours slodMesh collect ((clr as point3) / 255)
-- Collect vert-colours per face from main mesh:
local faceColours = for faceNum = 1 to slodMesh.numFaces collect
(
if not doFaces[faceNum] then undefined else
(
local faceMapVerts = meshOp.GetMapFace slodMesh 1 faceNum
for vertIdx = 1 to 3 collect
(
mapVertColours[faceMapVerts[vertIdx]]
)
)
)
*/
local defFaceClr = (gRsLodCombinerVals.defaultTint / 255)
for tintIdx = 1 to slodRef.tintMeshObjs.count do
(
local tintMesh = slodRef.tintMeshObjs[tintIdx].mesh
local usedMapVerts = #{}
local palVertColours = #()
usedMapVerts.count = palVertColours.count = (meshOp.getNumMapVerts tintMesh tintChan)
-- Collect tint vert-colours per face:
local tintFaceVerts = #()
tintFaceVerts.count = doFaces.count
for faceNum in doFaces collect
(
-- Colours from original texture:
--local texFaceColours = faceColours[faceNum]
local faceMapVerts = meshOp.GetMapFace tintMesh tintChan faceNum
local faceVerts = for vertIdx = 1 to 3 collect
(
local mapVertNum = faceMapVerts[vertIdx]
if (palVertColours[mapVertNum] == undefined) do
(
usedMapVerts[mapVertNum] = True
-- Get value from meshtint channel:
local tintClr = meshOp.getMapVert tintMesh tintChan mapVertNum
-- Add to palette's colour-set array:
palVertColours[mapVertNum] = (tintClr * defFaceClr) -- (tintClr * texFaceColours[vertIdx])
)
mapVertNum
)
tintFaceVerts[faceNum] = faceVerts
)
-- Clear meshtint colours...
--meshOp.setFaceColor tintMesh tintChan #all white
-- Convert colours to colour-map UVs:
local colourUvs = for item in palVertColours collect
(
if (item == undefined) then undefined else
(
local val = getBmpUVsForColour ((255 * item) as color)
--format "% :-> % :-> %\n" ((255 * item) as color) val (getBmpColourForUVs val)
val
)
)
local setChan = 1
-- Separate off each face on mapping-channel:
for faceNum = doFaces do
(
meshOp.setFaceColor tintMesh setChan faceNum black
)
-- Apply new uv/colour data:
for faceNum = doFaces do
(
local faceMapVerts = meshOp.GetMapFace tintMesh setChan faceNum
for vertIdx = 1 to 3 do
(
local clrMapFace = tintFaceVerts[faceNum]
local clrMapVert = clrMapFace[vertIdx]
local setVal = colourUvs[clrMapVert]
local mapVert = faceMapVerts[vertIdx]
meshOp.setMapVert tintMesh setChan mapVert setVal
)
)
)
)
)
(
local logMsg = "[/ApplyLodTints]"
gRsUlog.LogMessage logMsg context:"ComboLodder"
)
)
)
global gRsComboLodTint = RsComboLodTint()
struct RsComboLodFuncs
(
-- Reference to the tool's rollout:
toolRollout,
defIPLGroup = (GetAttrDefault "Gta Object" idxIPLGroup),
-- Gets highest drawable-distances for refDefs:
fn getRefLodDists refDefs =
(
local distRefDefs = #()
join distRefDefs refDefs
-- Get last drawable-ref for each ref:
local distRefDefs = #()
for refDef in refDefs do
(
if (refDef == undefined) then
(
format "Warning: Undefined ref!\n"
dontCollect
)
else
(
append distRefDefs refDef
local drawables = refDef.drawableLodRefs
if (drawables.count != 0) do
(
append distRefDefs drawables[drawables.count]
)
)
)
RSrefFuncs.loadLodDistances distRefDefs
-- Apply ComboLodder lod-distances to ref-data:
for refDef in refDefs where (refDef != undefined) do
(
local drawables = refDef.drawableLodRefs
refDef.comboLod.lodDistance = if (drawables.count == 0) then refDef.lodDistance else
(
drawables[drawables.count].lodDistance
)
)
return OK
),
-- Get the lod-distances, models and maximum face-sizes for billboarded SlodRef models (for use with ComboLodder)
fn loadSlodData refDefs forceLoad:False =
(
toolRollout = ::RsLodCombinerTool
-- Filter out refDefs that have already been processed:
refDefs = for ref in refDefs where (ref != undefined) and (forceLoad or (ref.comboLod == undefined)) collect ref
-- Cancel if there's nothing to do:
if (refDefs.count == 0) do return True
refDefs = makeUniqueArray refDefs
-- Load bounding-box data:
RsRefFuncs.LoadBoundingBoxes refDefs:refDefs
-- Get meshtint-palette filenames and counts:
RsRefFuncs.getPaletteCounts refDefs:refDefs
local tintVersion = gRsLodCombinerVals.meshTintVersion
local bmpHash = gRsComboLodTint.bmpHash
local refElems = RsRefFuncs.getRefElems refDefs
-- Set initial ref-values, and get list of refs' SlodRefs:
local loadSlodRefs = #()
for refIdx = 1 to refDefs.count do
(
local ref = refDefs[refIdx]
local refElem = refElems[refIdx]
ref.comboLod = ::RsLodCombinerRefData()
-- Initial check for non-comboloddable prop:
if (refElem != undefined) do
(
-- We won't ComboLod this prop if it has animation (unless it has 'Can ComboLod Animation' flag, when animation is small-scale enough to not matter for LOD)
ref.comboLod.hasAnim = ((refElem.getAttribute "Has Anim" False) or (refElem.getAttribute "AutoStart Anim" False)) and (not refElem.getAttribute "Can ComboLod Animation" False)
ref.comboLod.CanComboLod = (not ref.comboLod.hasAnim)
)
-- Only process this prop if it is still marked as valid:
if (ref.comboLod.CanComboLod) do
(
-- Set max bounding-box sizes for refs:
(
local refBox = ref.boundingBox
local maxBoxSize = aMax #(refBox[4] - refBox[1], refBox[5] - refBox[2], refBox[6] - refBox[3])
ref.comboLod.maxBoxSize = maxBoxSize
)
-- Load palette-hash data for refs:
local hasPals = (ref.paletteCount != 0)
if hasPals do
(
-- Get hash for lod_prop.bmp's colours - we'll need to regenerate meshtints if that changes:
if (bmpHash == undefined) do
(
gRsComboLodTint.loadBitmap()
bmpHash = gRsComboLodTint.bmpHash
)
-- Get hash of palette-data:
local pals = (gRsMeshTintFuncs.loadPalettesFromFile ref.paletteFile)
join pals #(tintVersion, bmpHash)
ref.comboLod.paletteHash = (getHashValue pals 1)
)
local slodRefLists = ref.slodRefs
for slodLevel = 1 to slodRefLists.count do
(
local slodList = slodRefLists[slodLevel]
for n = 1 to slodList.count do
(
-- If slod-level has a second ref, this must be a tiltable-alternative mesh:
if (n == 2) do
(
ref.comboLod.hasTiltAlt = True
ref.comboLod.maxTiltAltLevel = slodLevel
)
local slodRef = slodList[n]
-- Don't regenerate lod-data if it's already been loaded:
if (slodRef.comboLod == undefined) do
(
slodRef.comboLod = ::RsLodCombinerRefData()
append loadSlodRefs slodRef
-- Copy up meshtint-info from base-ref to slod-ref:
-- (this allows ComboLod meshtinting to access its palette-colours)
if hasPals do
(
slodRef.paletteCount = ref.paletteCount
slodRef.paletteFile = ref.paletteFile
slodRef.userPalFile = ref.userPalFile
slodRef.comboLod.paletteHash = 0
)
)
)
)
)
)
refElems.count = 0
-- Load new slodref models:
(
RSrefFuncs.loadModels loadSlodRefs promptText:"Loading LOD-source meshes"
)
-- If model-load was cancelled, return false:
if (not RSrefAllowUpdate) do return False
-- Collect flat material-list:
local refMats = #()
-- Fix materials, and measure maxFaceSize for newly-loaded billboard slod-source meshes:
for ref in loadSlodRefs do
(
-- Reorder and fill gaps in material-list, make sure there aren't any gaps in materialIdList:
if (isKindOf ref.material multiMaterial) then
(
local matList = ref.material.materialList
local matIdList = ref.material.materialIdList
local newMatList = #()
for idx = 1 to matIdList.count do
(
local matId = matIdList[idx]
newMatList[matId] = matList[idx]
)
local changeMade = (newMatList.count != matList.count)
-- Replace undefineds with default material:
for matIdx = 1 to newMatList.count where (newMatList[matIdx] == undefined) do
(
newMatList[matIdx] = gRsLodCombinerVals.getDefaultMat()
changeMade = True
)
-- Replace object's material-list if changes were made:
if changeMade do
(
ref.material.materialList = newMatList
ref.material.materialIdList = (for n = 1 to newMatList.count collect n)
)
join refMats matList
)
else
(
append refMats ref.material
)
-- Does this mesh use billboard materials?
local refHasBBs = (ref.meshObj != undefined) and (RsIsTreeBillboard ref.meshObj.mesh ref.material)
ref.comboLod.hasBBs = refHasBBs
-- Only process objects with valid meshes, using billboard-materials:
if refHasBBs do
(
local maxFaceSize = 0
local refMesh = ref.meshObj.mesh
local fullVertPosList = for vertNum = 1 to (getNumVerts refMesh) collect (getVert refMesh vertNum)
-- Find mesh's max edge-length:
for faceNum = 1 to (getNumFaces refMesh) do
(
local faceVerts = getFace refMesh faceNum
local vertPosList = for n = 1 to 3 collect fullVertPosList[faceVerts[n]]
for vertPair in #(dataPair vertPosList[1] vertPosList[2], dataPair vertPosList[2] vertPosList[3], dataPair vertPosList[1] vertPosList[3]) do
(
local vertDist = distance vertPair.v1 vertPair.v2
if (vertDist > maxFaceSize) do
(
maxFaceSize = vertDist
)
)
)
ref.comboLod.maxFaceSize = maxFaceSize
)
)
-- Set loaded materials to use this texture-scale:
pushPrompt "Setting texture-scaling..."
refMats = for mat in refMats where (isKindOf mat rage_shader) collect mat
RsScaleTexuresOnMaterials refMats gRsLodCombinerVals.textureScale quiet:True
refMats.count = 0
popPrompt()
-- Load lod-distance data:
getRefLodDists refDefs
local bbVersion = gRsLodCombinerVals.billboardMatVersion
-- Generate hashes for valid ref-data:
for ref in refDefs where (Ref.ComboLod.CanComboLod) do
(
local vals = stringStream ""
format "%#%#%" gRsLodCombinerVals.dataVersion ref.comboLod.lodDistance ref.comboLod.paletteHash to:vals
for slodIdx = 1 to ref.slodRefs.count do
(
format "#%" slodIdx to:vals
-- Each 'SlodRefs' level can list up to two sub-models
-- (one for tilted and one for non-tilted props)
for slodRef in ref.slodRefs[slodIdx] do
(
-- Mark prop as bad if it has a missing slod-mesh:
if (slodRef.MeshObj == undefined) do
(
Ref.ComboLod.HasMissingModel = True
Ref.ComboLod.CanComboLod = False
format "#MissingMesh" to:vals
)
-- Mark main ref as isTree if any slodrefs use billboards:
if (slodRef.comboLod.hasBBs) do
(
ref.comboLod.isTree = True
)
local slodBBVersion = if slodRef.comboLod.hasBBs then bbVersion else 0
format "#%#%#%#%" slodRef.name slodRef.meshTime slodRef.matTime slodBBVersion to:vals
)
local flaggedString = ""
if (ref.comboLod.isTree) do
(
format "#IsTree" to:vals
)
)
ref.comboLod.dataHash = (getHashValue (vals as string) 1)
)
return True
),
-- Returns given object's lod-distance for combolodding purposes:
fn getObjLodDistance obj =
(
local lodDist = if (isRsRef obj) then
(
getRefLodDists #(obj.refDef)
obj.refDef.comboLod.lodDistance
)
else
(
getAttr obj idxLodDistance
)
lodDist *= obj.scale.z
return lodDist
),
-- Sets appropriate Lod-distance and centre for a given set of props,
fn CalculateAndSetChildLodDist obj childlist =
(
(
local logMsg = "Calculating child-prop lod-distances:"
gRsUlog.LogMessage logMsg context:"ComboLodder"
)
local tempMesh = createInstance Editable_Mesh
-- Get lod-distances from objects:
for childObj in childlist do
(
local lodDist = getObjLodDistance childObj
-- Add lod-sphere to temp mesh:
local lodSphereMesh = manip.makeSphere childObj.pos lodDist 8
meshOp.attach tempMesh lodSphereMesh
)
-- Collect mesh-points:
local tempMeshPoints = for vert in tempMesh.mesh.verts collect vert.pos
-- Find minimum bounding-sphere for points:
local lodSphere = RsFindMinSphere tempMeshPoints showProgress:false
local propLodDist = lodSphere.radius
(
local logMsg = stringStream ""
format " Source-props lod-sphere:\tpos:%, radius:%\n" lodSphere.pos lodSphere.radius to:logMsg
logMsg = logMsg as string
gRsUlog.LogMessage logMsg context:"ComboLodder"
)
--sphere pos:lodsphere.pos radius:lodsphere.radius wirecolor:yellow -- Create debug-object
-- Move parent-object's pivot to centre of calculated sphere:
obj.pivot = lodSphere.pos
local LODMax = gRsLodCombinerVals.maxLodDist
if (propLodDist > LODMax) do
(
local logMsg = stringStream ""
format "Calculated LOD-distance for %'s props is very high: %m Are props-sources set up properly?" obj.name (integer propLodDist) to:logMsg
format " (setting to %m)" (integer LODMax) to:logMsg
gRsUlog.LogWarning logMsg context:childlist
propLodDist = LODMax
)
propLodDist = (propLodDist as Integer)
-- Set Lod-object's Child Lod distance:
setattr obj idxChildLodDistance propLodDist
-- Set child-prop Lod distances
for obj in childlist do
(
setattr obj idxInstLodDistance true
setattr obj idxLodDistance propLodDist
)
return true
),
------------------------------------------------------------------
-- Find and warn about lods with mixed IPL-groups:
------------------------------------------------------------------
fn FindMixedIplGroupLods mapNodes =
(
toolRollout = ::RsLodCombinerTool
(
gRsUlog.LogMessage "FindMixedIplGroupLods: Start" context:"ComboLodder" quiet:(not RsComboLodDebug)
)
local warningList = #()
struct sWarningItem (slodName, iplGroups, mapNode)
for mapNode in mapNodes do
(
for grp in (mapNode.GetGroupList()) do
(
local grpObjItems = grp.getObjs info:True
local defaultNum = 0
local iplGroups = #()
local iplGroupObjs = #()
-- Collect objects per IPL Group:
for objItem in grpObjItems do
(
local iplGroup = objItem.iplGroup
local iplNum = findItem iplGroups iplGroup
if (iplNum == 0) do
(
append iplGroups iplGroup
append iplGroupObjs #()
iplNum = iplGroups.count
)
append iplGroupObjs[iplNum] objItem.node
)
grpObjItems.count = 0
-- Add to warning-list if SLOD-group contained multiple IPL Groups:
if (iplGroups.count > 1) do
(
-- Sort IPL Group "undefined" (default) to top of list, and alphabetise the rest:
local grpWarnList = for n = 1 to iplGroups.count collect (dataPair iplName:iplGroups[n] objs:iplGroupObjs[n])
fn sortWarnList v1 v2 =
(
case of
(
(v1.iplName == "undefined"):-1
(v2.iplName == "undefined"):1
Default:(striCmp v1.iplName v2.iplName)
)
)
qsort grpWarnList sortWarnList
local tagIdx = grp.tagIdx
local slodName = if (tagIdx == undefined) then "..." else toolRollout.dataTags[tagIdx].nameText
Append warningList (sWarningItem slodName:slodName iplGroups:grpWarnList mapNode:mapNode)
)
)
)
if (warningList.count != 0) do
(
local checkedVals = #{}
local listItems = #()
local listItemObjs = #()
local listItemMaps = #()
for grpItem in warningList do
(
for iplItem in grpItem.iplGroups do
(
Append listItems #(grpItem.slodName, iplItem.iplName, (iplItem.objs.count as string) + " objects")
Append listItemObjs iplItem.objs
Append listItemMaps grpItem.mapNode
)
-- Tick all but first checkbox for this slodgroup:
checkedVals += #{(checkedVals.count + 2)..(checkedVals.count + grpItem.iplGroups.count)}
)
local msg = StringStream ""
local grpPlural = if (warningList.count == 1) then "SLOD groups were" else "SLOD group was"
format "% found containing objects with multiple IPL Group values.\n\n" grpPlural to:msg
format "Please put IPL Group objects in separate SLOD-groups.\nThe build will complain if a LOD-hierarchy contains multiple IPL Groups!\n\n" to:msg
format "Remove ticked objects from ComboLod tree, or just select them?" to:msg
local queryVal = RsQueryBoxMultiBtn (msg as string) title:"WARNING: SLOD-group contains multiple IPL Groups" timeout:-1 width:600 \
labels:#("Create new SLODs", "Select Objects", "Ignore") tooltips:#("Add ticked objects to new SLOD group(s)", "Select objects for ticked name", "Do nothing") \
listItems:listItems listTitles:#("", "SLOD group", "IPL Group", "Object Count") listCheckStates:checkedVals
if (queryVal != 3) do
(
if (queryVal == 1) then
(
local selMapNodes = #()
local objsPerMap = #()
for listNum in checkedVals do
(
local mapNode = listItemMaps[listNum]
local findIdx = FindItem selMapNodes mapNode
if (findIdx == 0) do
(
Append selMapNodes mapNode
Append objsPerMap #()
findIdx = selMapNodes.count
)
Join objsPerMap[findIdx] listItemObjs[listNum]
)
-- Create new group(s) for these objects
for idx = 1 to selMapNodes.count do
selMapNodes[idx].CreateGroups objsPerMap[idx]
toolRollout.RefreshTreeView()
)
else if (queryVal == 2) do
(
local selObjs = #()
for listNum in checkedVals do
Join selObjs listItemObjs[listNum]
Select selObjs
)
)
)
(
gRsUlog.LogMessage "FindMixedIplGroupLods: End" context:"ComboLodder" quiet:(not RsComboLodDebug)
)
),
-- Collects IPL Groups used by the given map-nodes and nodes in the scene,
-- and works out if more than 1000 objects will end up with a specific IPL
fn FindExcessiveIplGroups mapNodes =
(
PushPrompt "Counting IPL Group uses..."
local allGrps = #()
for mapNode in mapNodes do
Join allGrps (mapNode.GetSubGroups recurse:True)
-- Ignore lod-groups that don't have IPL Group set
allGrps = for grp in allGrps where (grp.blockIplName != Undefined) and (grp.blockIplName != "undefined") collect grp
-- Ignore lod-groups that won't generate a lod-object
allGrps = for grp in allGrps where grp.dummySlod or grp.lodLevelsUsed[grp.genLodLevel] collect grp
-- Collect IPL Groups used in ComboLodder groups, and the number of objects that they (will) use
local iplGroups = #()
local objCountPerIpl = #()
for grp in allGrps do
(
local iplIdx = FindItem iplGroups grp.blockIplName
if (iplIdx == 0) then
(
Append iplGroups grp.blockIplName
Append objCountPerIpl 0
iplIdx = iplGroups.count
)
-- This lod-group will generate one mesh
objCountPerIpl[iplIdx] += 1
)
-- Collect all existing lod-objects
-- (We will ignore these when collecting nodes from scene)
local existingLods = #()
for mapNode in mapNodes do
Join existingLods (mapNode.GetLodObjs recurse:True)
-- Find scene-objects that also use ComboLodded ipls
for obj in Geometry where (FindItem existingLods obj == 0) and (GetAttrClass obj == "Gta Object") do
(
local objIpl = GetAttr obj idxIPLGroup
local iplIdx = Finditem iplGroups objIpl
-- Increment count if it's one of the combolodded ipls
if (iplIdx != 0) do
objCountPerIpl[iplIdx] += 1
)
-- Update iplObjCounts for top-level SLOD-groups
for mapNode in mapNodes do
(
for grp in (mapNode.GetSubGroups recurse:False) do
(
local thisIplCount = 0
local iplIdx = FindItem iplGroups grp.blockIplName
if (iplIdx != 0) do
thisIplCount = objCountPerIpl[iplIdx]
grp.iplObjCount = thisIplCount
)
)
PopPrompt()
return OK
),
------------------------------------------------------------------
-- Find and warn about props that lie a ways outside their container-lod's bounds:
------------------------------------------------------------------
fn FindOutlyingProps mapNodes =
(
toolRollout = ::RsLodCombinerTool
(
gRsUlog.LogMessage "FindOutlyingProps: Start" context:"ComboLodder" quiet:(not RsComboLodDebug)
)
local warningList = #()
for mapNode in mapNodes do
(
for grp in (mapNode.GetGroupList()) where (grp.containerLodParent != undefined) and (isValidNode grp.containerLodParent.node) do
(
local lodProxy = grp.containerLodParent.node
local objMesh = (copy lodProxy.mesh)
local vertDists = for VertNum = 1 to objMesh.NumVerts collect (length (getVert objMesh VertNum))
local spherePos = lodProxy.pos
local sphereRadius = (aMax vertDists) + 50
-- Find objects in group that are outside this sphere:
local outsideObjs = #()
for obj in (grp.getObjs info:False) do
(
if (distance obj.pos spherePos) > sphereRadius do
(
append outsideObjs obj
)
)
-- Add to warning-list if outlying objects were found:
if (outsideObjs.count != 0) do
(
local tagIdx = grp.tagIdx
local slodName = if (tagIdx == undefined) then "..." else toolRollout.dataTags[tagIdx].nameText
append warningList (dataPair slodName:slodName objs:outsideObjs)
)
)
)
if (warningList.count != 0) do
(
local listItems = #()
local listItemObjs = #()
local allObjs = #()
for item in warningList do
(
append listItems #(item.slodName, (item.objs.count as string) + " objects")
append listItemObjs item.objs
join allObjs item.objs
)
local msg = StringStream ""
local grpPlural = if (warningList.count == 1) then "SLOD groups were" else "SLOD group was"
format "% found containing objects lying away from their ContainerLod Proxy.\n\n" grpPlural to:msg
format "Please consider linking these props to a more appropriate proxy!\n\n" to:msg
format "Select these props, or ignore?" to:msg
local queryVal = RsQueryBoxMultiBtn (msg as string) title:"WARNING: SLOD-group has outlying props" timeout:-1 width:600 \
labels:#("Select Objects", "Ignore") tooltips:#("Select objects for ticked name", "Select ticked objects and remove from ComboLodder tree", "Do nothing") \
listItems:listItems listTitles:#("SLOD group", "Object Count")
if (queryVal == 1) do
(
clearListener()
select allObjs
)
)
(
gRsUlog.LogMessage "FindOutlyingProps: End" context:"ComboLodder" quiet:(not RsComboLodDebug)
)
),
------------------------------------------------------------------
-- Update all LOD-links, and set distances on some props:
------------------------------------------------------------------
fn UpdateLodLinks mapNodes silent:False firstLodUpdate:False updateLodHashes:#() =
(
local parChangesMade = 0
local waterChangesMade = 0
local lodFlagChangesMade = 0
local AOChangesMade = 0
local matNameChangesMade = 0
pushPrompt "Updating LOD-links..."
local contLodObjs = #()
local topSlodObjs = #()
local setLodDistObjItems = #()
-- Used to print "renaming" title only once:
local doingRename = false
-- Are we currently working on a DLC project?
local isDLC = RsIsDLCProj()
for mapNum = 1 to mapNodes.count do
(
local mapNode = mapNodes[mapNum]
local mapGrpList = mapNode.GetGroupList()
-- This map may have a shorter name that we use for naming objects
local shortMapName = mapNode.mapName
local fullMapName = mapNode.fullMapName
local hasShortMapName = (shortMapName != fullMapName)
for grpNum = 1 to MapGrpList.count do
(
local grpData = MapGrpList[grpNum]
-- Get ContainerLod object for this lod-group:
local contLodItem = grpData.containerLodParent
local contLodObj = if (contLodItem != undefined) and (RsIsContainerLod contLodItem.node) then contLodItem.node else undefined
if (contLodObj != undefined) and (contLodObj.refDef != undefined) do
(
appendIfUnique contLodObjs contLodObj
)
-- Get list of all sub-groups:
local lodItems = #(grpData)
join lodItems (grpData.getSubGroups recurse:true)
grpData = undefined
local natAOMult = gRsLodCombinerVals.natAOMult
local artAOMult = gRsLodCombinerVals.artAOMult
for item in lodItems do
(
local lodObj = (item.getLodObjs recurse:false)[1]
if (isValidNode lodObj) do
(
-- Make sure that lod-object's flags are as they should be:
(
-- Set DLC flags
(
SetAttr lodObj idxNewDlc isDLC
SetAttr lodObj idxTxdDlc isDLC
)
-- Sets "Dont Render In Water Reflections" on all LOD-blocks -unless- they
-- have "Generate Water Reflective LODs" ticked on one of their source-props:
(
local dontWaterReflect = item.dontWaterReflect
if (not silent) and (getAttr lodObj idxDontRenderInWaterReflections != dontWaterReflect) do
(
waterChangesMade += 1
)
setAttr lodObj idxDontRenderInWaterReflections dontWaterReflect
)
-- Flag this object as being a ComboLodder-generated object:
(
if (not silent) and (not (getAttr lodObj idxIsComboLod)) do
(
lodFlagChangesMade += 1
)
setAttr lodObj idxIsComboLod True
)
-- Set Ambient Occlusion Multiplier values:
(
if (not silent) do
(
if
(
(not (getAttr lodObj idxEnableAmbientMultiplier)) or
(getAttr lodObj idxAOMultiplier != natAOMult) or
(getAttr lodObj idxArtificialAOMultiplier != artAOMult)
)
do
(
AOChangesMade += 1
)
)
setAttr lodObj idxAOMultiplier natAOMult
setAttr lodObj idxArtificialAOMultiplier artAOMult
setAttr lodObj idxEnableAmbientMultiplier True
)
-- Turn off 'Remove Degenerate Polys' attribute for this object, as that will break tree-billboards:
(
setAttr lodObj idxRemoveDegeneratePolys False
)
)
-- Set 'IPL Group' attribute, taken from lod's source-props:
local iplGroupVal = item.blockIPLName
if (not IsKindOf iplGroupVal String) do
(
-- Use default 'IPL Group' value if valid name wasn't found:
iplGroupVal = defIPLGroup
)
SetAttr lodObj idxIPLGroup iplGroupVal
-- Set mesh's TXD attribute (texture dictionary)
local lodSuffix = gRsLodCombinerVals.lodSuffixes[item.genLodLevel]
local txdMidToken = if (contLodObj == Undefined) then "_combo_" else "_"
local txdStr = (mapNode.mapname + txdMidToken + lodSuffix)
SetAttr lodObj idxTXD txdStr
-- Make sure that lod-object name is in-sync:
if (lodObj.name != item.lodObjName) do
(
if (not doingRename) do
(
format "\nRenaming LOD-objects:\n"
doingRename = true
)
format " % => %\n" lodObj.name item.lodObjName
lodObj.name = item.lodObjName
)
-- Slod-objects with no available lod-parent will be linked to the ContainerLod proxy, if found:
local parentLodObj = ((item.GetParentData()).getLodObjs recurse:false)[1]
parentLodObj = case of
(
(isValidNode parentLodObj):(parentLodObj) -- Parent to parent's LOD-object
(item.genLodLevel != 1):(append topSlodObjs lodObj; contLodObj) -- Make SLODs (but not level-1 LODs) be children of containerLOD, if used
Default:undefined
)
if (not isValidNode parentLodObj) do (parentLodObj = undefined)
local currParent = RsSceneLink.GetParent LinkType_LOD lodObj
-- Set/unset parent for lod-object:
if (parentLodObj != undefined) then
(
if (currParent != parentLodObj) do
(
RsSceneLink.AddContainer LinkType_LOD lodObj
RsSceneLink.SetParent LinkType_LOD lodObj parentLodObj
parChangesMade += 1
)
)
else
(
if (currParent != undefined) do
(
RsSceneLink.RemoveContainer LinkType_LOD lodObj
parChangesMade += 1
)
)
-- Set material-name (will include full container-name, to avoid auto-renaming on export)
(
local matName = item.lodObjName + "_Material"
-- Swap short mapname-prefix for the full map-name when naming materials
if hasShortMapName do
(
matName = SubString matName (shortMapName.count + 1) -1
matName = fullMapName + matName
)
if (lodObj.material.name != matName) do
(
lodObj.material.name = matName
matNameChangesMade += 1
)
)
)
)
)
for objData in (mapNodes[mapNum].getObjs info:True) do
(
local obj = objData.node
if (isValidNode obj) do
(
-- Make sure all props are linked to their parent-LODs:
(
local parentLodObj = if (not objData.canLod) then undefined else ((objData.GetParentData()).getLodObjs recurse:false)[1]
if (not isValidNode parentLodObj) do (parentLodObj = undefined)
local currParent = RsSceneLink.GetParent LinkType_LOD obj
if (parentLodObj != undefined) then
(
if (currParent != parentLodObj) do
(
RsSceneLink.AddContainer LinkType_LOD obj
RsSceneLink.SetParent LinkType_LOD obj parentLodObj
parChangesMade += 1
)
)
else
(
if (currParent != undefined) do
(
RsSceneLink.RemoveContainer LinkType_LOD obj
parChangesMade += 1
)
)
)
-- Set jittered lod-distances on props that are too small to show at lod-level 1:
if (objData.tooSmallToShow) do
(
append setLodDistObjItems objData
)
)
)
)
local lodDistsChanged = 0
-- Set lod-distances on objects that were orphaned due to being too small:
for objData in setLodDistObjItems do
(
local obj = objData.node
local currentDist = getattr obj idxLodDistance
-- Jitter lod-distance 0-25%, using node-transform as random seed:
seed (getHashValue obj.transform 1)
local jitterVal = random 0.75 1.0
local refLodDist = objData.getLodDistance()
local propLodDist = integer (refLodDist * jitterVal)
if (propLodDist != currentDist) do
(
(
local logMsg = stringStream ""
format "Changing lod-distance for %: %m -> %m (%m jittered by %\%)\n" obj.name currentDist propLodDist refLodDist(formattedPrint (100 * (1.0 - jitterVal)) format:".1f") to:logMsg
logMsg = logMsg as string
gRsUlog.LogMessage logMsg context:"ComboLodder"
)
lodDistsChanged += 1
setattr obj idxInstLodDistance true
setattr obj idxLodDistance propLodDist
)
)
local contLodChanges = 0
-- Set ContainerLod Child LOD distances on their children:
if (contLodObjs.count != 0) do
(
for contLodObj in contLodObjs do
(
local lodDist = contLodObj.refDef.childLodDistance
local changedChilds = #()
-- Only do this for combolodder-system objects:
for childObj in (RsGetLODChildren contLodObj) where ((findItem topSlodObjs childObj) != 0) do
(
local currentVal = getattr childObj idxLodDistance
if (currentVal != lodDist) do
(
setattr childObj idxLodDistance lodDist
append changedChilds childObj
contLodChanges += 1
)
)
)
)
-- If function was triggered by lod-generation, update the lod-hashes to match latest data:
for lodObjItem in updateLodHashes do
(
local ItemParent = (lodObjItem.GetParentData())
lodObjItem.sourceHash = ItemParent.lodObjHash = ItemParent.sourceHash
)
-- Mark mapnodes as having large IPL Groups, if they do
this.FindExcessiveIplGroups mapNodes
-- Update the Scene Lod Editor rollout, if it's open:
if (::RsSceneLodEditorRoll != undefined) and (RsSceneLodEditorRoll.open) do
(
RsSceneLodEditorRoll.updateSelection()
)
popPrompt()
-- Throw up message if changes were made on tool-open:
if firstLodUpdate and (not silent) do
(
local changed = False
local msg = stringStream ""
(
Format "ComboLodder has automatically fixed some LOD stuff:\n\n" to:msg
for item in
#(
DataPair count:parChangesMade msg:"Reassigned LOD parenting",
DataPair count:contLodChanges msg:"ContainerLod child-distances updated",
DataPair count:lodDistsChanged msg:"Small-orphan lod-distances updated",
DataPair count:waterChangesMade msg:"\"Dont Render In Water Reflections\" flag toggled",
DataPair count:lodFlagChangesMade msg:"\"Is ComboLod\" flag set",
DataPair count:AOChangesMade msg:"Ambient Occlusion attributes changed",
DataPair count:matNameChangesMade msg:"Materials renamed"
)
where (item.count != 0) do
(
Format "* % (% object%)\n" item.msg item.count (if (item.count == 1) then "" else "s") to:msg
changed = True
)
Format "\n(changes are only made to objects included in ComboLodder system)" to:msg
)
if changed do
MessageBox (msg as string) title:"ComboLodder: Updates made"
-- Check for and warn about various problems:
this.FindMixedIplGroupLods mapNodes
--this.FindOutlyingProps mapNodes
)
firstLodUpdate = False
(
local logMsg = "UpdateLodLinks: End\n"
gRsUlog.LogMessage logMsg context:"ComboLodder"
)
OK
),
-- Checks Memory Resident object-lists out from Perforce:
fn editResidentLists lodNodes xmlAddQueue: =
(
local topParentIdxs = #{}
local xmlFilenames = #()
-- Collect top-parent idxs for generated nodes:
for lodNode in lodNodes do
(
local ParentItem = LodNode.GetData()
if not (isProperty ParentItem #dataNode) do
(
ParentItem = ParentItem.GetRootData()
)
local tagIdx = parentItem.tagIdx
if not topParentIdxs[tagIdx] do
(
local xmlFilename = (parentItem.getResidentFilename())
if (xmlFilename != undefined) do
(
append xmlFilenames xmlFilename
)
topParentIdxs[tagIdx] = True
)
)
-- Returns False if perforce-dialog is cancelled:
if not (gRsPerforce.readOnlyP4Check xmlFilenames queue:xmlAddQueue exclusive:false) do (return False)
return topParentIdxs
),
------------------------------------------------------------------
-- Values used by LOD-generator:
------------------------------------------------------------------
matEditWasOpen, viewSave, progressVal, progressMax, hideObjs, genCount,
------------------------------------------------------------------
-- Used to makes sure channels are active on a given mesh:
------------------------------------------------------------------
fn activateChannels theMesh =
(
local uvChans = #{uvChannel_planar, uvChannel_texmap, uvChannel_scale, uvChannel_axisAligned}
-- Activate channels, Defaulting them to black:
for chan in uvChans do
(
if not (meshOp.getMapSupport theMesh chan) do
(
meshOp.setFaceColor theMesh chan #all black
)
)
),
------------------------------------------------------------------
-- Recursive portion of ComboLod model-generator:
------------------------------------------------------------------
fn GenerateLods_Rec lodNodes: force:TRUE doChildren:true hidePropsAfter:false =
(
local keepGoing = True
for lodNum = 1 to lodNodes.count while keepGoing do
(
-- Recursively generate lods for child-nodes first:
if doChildren do
(
local childTagIdxs = lodNodes[lodNum].childTagIdxs
for tagIdx in childTagIdxs while keepGoing do
(
local childTag = toolRollout.dataTags[tagIdx]
if (childTag.type != #object) do
(
keepGoing = GenerateLods_Rec lodNodes:#(childTag) force:force doChildren:true
)
)
)
local lodNodeData = lodNodes[lodNum].GetData()
local topNode = lodNodeData.GetRootData()
local shortMapName = topNode.mapName
local fullMapName = topNode.fullMapName
-- Set lodSynced value for node-data:
-- (If object-node, sets sourceHash value)
lodNodeData.UpdateVals()
local lodLevel = lodNodeData.genLodLevel -- Lod-level object to be generated by this node
-- Number of props per lod-level for this node (n=1 is no-objects slot in original array)
local objsPerLodLevel = for n = 2 to lodNodeData.lodLevelsCount.count collect lodNodeData.lodLevelsCount[n]
-- Generate lod-objects for out-of-date non-object nodes:
if keepGoing and (lodLevel != 0) and (force or (lodNodeData.lodSynced != true)) do
(
-- Remove existing lod-objects from current datastruct:
lodNodeData.deleteLods()
-- Is this node going to get a single-face dummy-object?
local dummySlod = lodNodeData.dummySlod
-- Get valid objects in this block/group:
local nodeObjItems = if (dummySlod) then
(
-- For dummySlod, get list of objects for lower lod-level:
lodNodeData.getObjs info:true lodFilter:1
)
else
(
lodNodeData.getObjs info:true lodFilter:true
)
nodeObjItems = for item in nodeObjItems where (item.canLod and (isValidNode item.node)) collect item
-- Skip generation if block/group has no suitable source-props (unless it's going to get a dummy-object):
if (nodeObjItems.count != 0) do
(
-- Objects in hideObjs will be hidden at end of process:
if hidePropsAfter and not dummySlod do
(
local visibleProps = for objItem in nodeObjItems where (not objItem.Node.isHidden) collect objItem.Node
join hideObjs visibleProps
)
-- Get sub-lod object-positions for generating DummySlod:
local objCont = undefined
local objPosList = #()
if (dummySlod) do
(
objPosList = for objItem in nodeObjItems collect objItem.node.pos
objCont = RsGetObjContainer nodeObjItems[1].node
)
-- Collect list of objects to be used to generate each of this generator-node's lod-levels:
local toLodObjItems = for objItem in nodeObjItems where (objItem.sourceLodLevels >= lodLevel) collect objItem
local toLodObjs = for objItem in toLodObjItems collect objItem.node
-- This list is no longer required:
nodeObjItems = undefined
if (dummySlod) or (toLodObjs.count != 0) do
(
-- Get prop-objects' container:
if (objCont == undefined) do
(
objCont = RsGetObjContainer toLodObjs[1]
)
-- Choose name for object:
local lodObjName = lodNodeData.ShowName() -- Make sure lodObjName is up-to-date
if (genCount == 0) do
(
format "Generating ComboLod objects:\n"
)
genCount += 1
(
local logMsg = "Generate LOD: " + (lodObjName as string)
gRsUlog.LogMessage logMsg context:"ComboLodder" quiet:False
)
local comboLodObj = editable_mesh wireColor:(random black white) material:(multiMaterial()) name:lodObjName
-- Set transform-locks:
setTransformLockFlags comboLodObj #{1..9}
-- Add object-item struct to data-node's lod-obj list:
local lodObjItem = ::RsLodCombinerObject.create comboLodObj parentData:lodNodeData
lodObjItem.lodLevel = lodLevel
lodNodeData.lodObjItem = lodObjItem
-- Add new object to prop-objects' container:
if (objCont != undefined) do
(
objCont.addNodeToContent comboLodObj
)
-- Add initial tri to mesh, so that attaching works properly on UVs:
setMesh comboLodObj verts:#([0,0,0], [1,1,0], [1,0,0]) faces:#([1,2,3])
activateChannels comboLodObj
-- Generate objects to attach into comboLodObj:
local attachObjList = #()
gRsUlog.LogMessage "Creating temp objects for attach" context:"ComboLodder"
for objNum = 1 to toLodObjItems.count while keepGoing and (keepGoing = progressupdate ((100.0 * (progressVal += 1)) / progressMax)) do
(
local objItem = toLodObjItems[objNum]
local obj = objItem.node
local slodLevelRefs = obj.refDef.slodRefs[lodLevel]
local slodIdx = if objItem.useTiltAlt and (slodLevelRefs[2] != undefined) then 2 else 1
local lodRef = slodLevelRefs[slodIdx]
local tempObj = Editable_Mesh name:(uniqueName ("TEMP_" + lodRef.name + "_"))
tempObj.material = if (lodRef.material == undefined) then (standardMaterial()) else lodRef.material
-- Use tint-mesh, if available:
if (lodRef.tintMeshObjs != undefined) and (lodRef.tintMeshObjs.count != 0) then
(
local tintMeshNum = (objItem.tintIdx + 1)
if (tintMeshNum > lodRef.tintMeshObjs.count) do (tintMeshNum = lodRef.tintMeshObjs.count)
-- Use tint-mesh:
tempObj.mesh = copy lodRef.tintMeshObjs[tintMeshNum].mesh
)
else
(
tempObj.mesh = copy lodRef.meshObj.mesh
)
-- Does this prop-lod mesh have billboard materials?
local lodHasBBmats = lodRef.comboLod.hasBBs
local matList
local objXform = objItem.lodXform()
local bbVerts = #{}
bbVerts.count = tempObj.numVerts
-- Get material-list for billboards and first attach-object:
if lodHasBBmats or (objNum == 1) do
(
local matFaces = #()
matList = RsGetMaterialsOnObjFaces tempObj faceLists:matFaces
-- Collect and transform billboard-faces:
if lodHasBBmats do
(
local objScale = objXform.scalePart
local bbFaces = #{}
for matNum = 1 to matList.count do
(
local thisMat = matList[matNum]
if (RsIsBillboardMat thisMat) do
(
bbFaces += matFaces[matNum]
)
)
local doneFaces = #{}
doneFaces.count = bbFaces.count
local vertCount = tempObj.numVerts
for faceNum = bbFaces where not doneFaces[faceNum] do
(
-- Find billboard-element:
local elemFaces = meshop.getElementsUsingFace tempObj #{faceNum}
doneFaces += elemFaces
local elemVerts = #{}
elemVerts.count = vertCount
for thisFace in elemFaces do
(
elemVerts += (meshop.getVertsUsingFace tempObj thisFace)
)
-- We want to keep a list of all billboard-face verts:
bbVerts += elemVerts
-- Get array of element-vert positions:
local vertList = for vertNum = elemVerts collect (dataPair num:vertNum pos:(meshOp.getVert tempObj vertNum))
-- Find element's mid-point:
local midPos = [0,0,0]
for item in vertList do
(
midPos += item.pos
)
midPos /= vertList.count
-- Find transform for face-verts:
local facePos = (midPos * objXform)
local faceXform = scaleMatrix objScale
translate faceXform facePos
-- Move face-verts:
for item in vertList do
(
local newPos = (item.pos - midPos)
newPos *= faceXform
meshOp.setVert tempObj item.num newPos
)
)
)
)
if (bbVerts.numberSet == 0) then
(
-- If object didn't have billboards, just transform the whole object:
tempObj.transform = objXform
)
else
(
-- Transform non-billboard verts, after billboards have been done separately:
for vertNum in -bbVerts do
(
local newPos = ((meshOp.getVert tempObj vertNum) * objXform)
meshOp.setVert tempObj vertNum newPos
)
)
-- Replace comboLodObj's material-list with materials used on its first attach-object:
-- (this gets rid of its unused first material)
if (objNum == 1) do
(
comboLodObj.material.materialList = matList
comboLodObj.material.materialIdList = (for n = 1 to matList.count collect n)
)
append attachObjList tempObj
)
-- Attach all meshes together to form Combined-LOD object.
gRsUlog.LogMessage "Attaching temp objects together" context:"ComboLodder"
for objNum = 1 to attachObjList.count while keepGoing and (keepGoing = progressupdate ((100.0 * (progressVal += 1)) / progressMax)) do
(
local obj = attachObjList[objNum]
meshop.attach comboLodObj obj attachMat:#MatToID deleteSourceNode:true
)
gRsUlog.LogMessage "Attach completed" context:"ComboLodder"
-- Clean up, if cancelled:
if not keepGoing then
(
delete comboLodObj
delete (for obj in attachObjList where isValidNode obj collect obj)
)
else
(
if (dummySlod) then
(
-- DummySlod material is set to default-lod:
comboLodObj.material.materialList = #(gRsLodCombinerVals.getDefaultMat())
)
else
(
-- Remove initial safety-face from mesh:
meshop.deleteFaces comboLodObj #{1} delIsoVerts:true
)
-- Set mesh's Vertex Illumination channel (Artificial AO) to default dark grey:
meshOp.setFaceColor comboLodObj -1 #all (color 30 30 30)
-- Set mesh's Vertex Colour channel (Natural AO) channel to default white:
meshOp.setFaceColor comboLodObj 0 #all White
-- We're going to replace uses of "lod_prop_TINTED" bitmap with "lod_prop" bitmap:
local lodTintPattern = gRsLodCombinerVals.lodTintPattern
-- Mark materials as requiring default lod-texture material if they're undefined, or are using meshtint-tag texture-filename:
local objMat = comboLodObj.material
local matList = objMat.materialList
local setToDefaults = #{}
for matIdx = 1 to matList.count do
(
local mat = matList[matIdx]
local setToDefault = False
case (classOf mat) of
(
UndefinedClass:
(
setToDefault = True
)
Rage_Shader:
(
if (matchPattern (RstGetShaderName mat) pattern:"default.*") do
(
local subTexMap = getSubTexmap mat 1
if (subTexMap != undefined) and (matchPattern subTexMap.filename pattern:lodTintPattern) do
(
setToDefault = True
)
)
)
)
setToDefaults[matIdx] = setToDefault
)
-- Replace marked materials with default-texture material:
if (setToDefaults.count != 0) do
(
for matIdx = setToDefaults do
(
objMat.materialList[matIdx] = gRsLodCombinerVals.getDefaultMat()
)
)
-- Find billboard materials
-- Correct materials with "trees_lod2d" shaders to use "trees_lod2"
local hasBBmats = False
for mat in comboLodObj.material.materialList where (RsIsBillboardMat mat) do
(
hasBBmats = True
local shaderName = RstGetShaderName mat
if (matchPattern shaderName pattern:"trees_lod2d*") do
(
RstSetShaderName mat ("trees_lod2" + (getFilenameType shaderName))
)
)
-- Set up billboard-material bits:
if hasBBmats do
(
gRsUlog.LogMessage "Billboard setup: Start" context:"ComboLodder"
local worldNormal = lodNodeData.worldNormal
local doSetNormal = (worldNormal != undefined)
local matList = comboLodObj.material.materialList
for matNum = 1 to matList.count do
(
-- Make all materials unique, so they can be safely edited without messing up other objects' materials:
local subMat = copy matList[matNum]
matList[matNum] = subMat
-- Set the world-normal on TreeLod2 shaders, if provided:
if doSetNormal and RsIsBillboardMat subMat do
(
for varNum = 1 to RstGetVariableCount subMat do
(
local varName = RstGetVariableName subMat varNum
case varName of
(
"TreeLOD2 World Normal":(RstSetVariable subMat varNum worldNormal)
"UseTreeNormals":(RstSetVariable subMat varNum 0.0)
)
)
)
)
gRsUlog.LogMessage "RsCopyBillboardDimensionsToShaders" context:"ComboLodder"
RsCopyBillboardDimensionsToShaders #(comboLodObj)
gRsUlog.LogMessage "RsWalkBillboardMtls" context:"ComboLodder"
RsWalkBillboardMtls comboLodObj
gRsUlog.LogMessage "Billboard setup: End" context:"ComboLodder"
)
-- Set "Superlod with Alpha" attribute on non-tree super-lods that have alpha:
if (not hasBBmats) and (lodLevel != 1) and (RsCheckIsAlphad comboLodObj) do
(
format "\tSetting \"Superlod with Alpha\" attribute\n"
setAttr comboLodObj idxSuperLodAlpha True
)
-- Set up LOD-data for new object...
-- Get list of child-objects for new object:
local childObjs = #()
if (lodLevel == 1) then
(
-- 1st-level LODs are linked direct to the props:
childObjs = toLodObjs
)
else
(
for subItem in (lodNodeData.GetGroupList()) do
(
join childObjs (subItem.getLodObjs recurse:false)
)
)
-- Set lod-distance on new object:
local lodDist = gRsLodCombinerVals.lodDistUnits[lodLevel]
setAttr comboLodObj idxLodDistance lodDist
-- Set object pivot and child-lod distances:
if (lodLevel == 1) then
(
-- Set prop-lod values, and set LOD-object's pivot:
calculateAndSetChildLodDist comboLodObj childObjs
)
else
(
-- Set SLOD-object's pivot to centre of bounding-cylinder:
local meshVerts = if (dummySlod) then objPosList else (for vert in comboLodObj.verts collect vert.pos)
-- Mid-point of cylinder will be halfway between max/min z-positions:
local zPosVals = for pos in meshVerts collect pos.z
local minZ = (aMin zPosVals)
local maxZ = (aMax zPosVals)
local midZ = minZ + (0.5 * (maxZ - minZ))
-- Flatten position-values, so vert-heights won't affect calculated sphere
meshVerts.Z = midZ
local minSphere = RsFindMinSphere meshVerts showProgress:false
-- Move dummySlod's face to below lowest point:
if (dummySlod) do
(
comboLodObj.pos = minSphere.pos
comboLodObj.pos.Z = (minZ - 20)
)
-- Move pivot to centre of minimal lod-sphere:
comboLodObj.pivot = minSphere.pos
-- Set child-lod distance on new object, and copy that to children's lod-distances:
local childLodDist = gRsLodCombinerVals.lodDistUnits[lodLevel - 1]
setAttr comboLodObj idxChildLodDistance childLodDist
for childObj in childObjs do
(
setAttr childObj idxInstLodDistance True
setAttr childObj idxLodDistance childLodDist
)
)
-- Reset Xforms and collapse modifier:
ResetXForm comboLodObj
convertToPoly comboLodObj
-- PARENT/CHILD HIERARCHY-LINKS ARE SET UP LATER BY updateLodLinks() --
if hidePropsAfter do
(
-- append hideObjs comboLodObj
)
(
-- Set material-name (this will be updated later if needed by UpdateLodLinks)
comboLodObj.material.name = (lodObjName + "_Material")
)
-- Make sure all submaterials are set to show properly in viewport:
for subMat in comboLodObj.material.materialList where (isKindOf subMat Rage_Shader) and (not RstGetHwRenderMode subMat) do
(
enablehardwarematerial subMat True
RstSetHwRenderMode subMat True
)
)
)
if keepGoing then
(
-- Update node's sourceHash to reflect state of objects at time of lod-generation:
local currentSourceHash = lodNodeData.getSourceHash getFresh:true
lodNodeData.sourceHash = lodNodeData.lodObjHash = lodNodeData.lodObjItem.sourceHash = currentSourceHash
lodNodeData.lodSynced = true
)
else
(
lodNodeData.lodObjItem = undefined
lodNodeData.sourceHash = lodNodeData.lodObjHash = 0
lodNodeData.lodSynced = false
)
gc()
)
)
)
return keepGoing
),
fn GenerateLods_Start =
(
-- Set up struct-values to be shared with GenerateLods_Rec:
toolRollout = ::RsLodCombinerTool
hideObjs = #()
genCount = 0
-- Deactivate tool-window's callbacks:
toolRollout.callbacksActive = false
-- Close material-editor, to stop it flashing during material-cleanups:
matEditWasOpen = MatEditor.isOpen()
MatEditor.Close()
-- Set all viewports to Wireframe mode, so it doesn't slow down while it's working:
viewSave = RSrefFuncs.viewportsToWire()
),
fn GenerateLods_End =
(
-- Reopen material-editor (if it was open earlier), revert viewports, and reactivate tool's callbacks:
if matEditWasOpen do MatEditor.Open()
RSrefFuncs.restoreViewports viewSave
toolRollout.callbacksActive = true
-- Clear shared values, we don't want them hanging around:
toolRollout = undefined
hideObjs = #()
viewSave = undefined
),
------------------------------------------------------------------
-- Generate combo-lod objects for tree-nodes:
------------------------------------------------------------------
fn GenerateLods doEndFunc:True lodNodes: force:TRUE doChildren:true doMetaExport:True hidePropsAfter:false =
(
local timeStart = timeStamp()
local keepGoing = True
undo off
(
-- Perforce check-out/add Memory Resident lists for maps, and return
-- list indexes of relevant map-data nodes for post-generation processing:
local xmlAddQueue = #()
local topParentIdxs = #{}
if doMetaExport then
(
topParentIdxs = editResidentLists lodNodes xmlAddQueue:xmlAddQueue
)
else
(
for lodNode in lodNodes do
(
local ParentItem = LodNode.GetData()
if not (isProperty ParentItem #dataNode) do
(
ParentItem = ParentItem.GetRootData()
)
local parentIdx = parentItem.tagIdx
topParentIdxs[parentIdx] = True
)
)
-- Abort if metafile-checkout step was cancelled/failed:
if (topParentIdxs == False) do
(
return False
)
pushPrompt "Starting LOD-generation..."
GenerateLods_Start()
-- Clear child-nodes from list, if we're going to be recursing:
if doChildren do
(
local lodNodeTagIdxs = for item in lodNodes collect item.tagIdx
local lodDataList = for item in lodNodes collect (item.GetData())
local dataNum = 0
local selItemCount = lodDataList.count
while (dataNum < lodDataList.count) do
(
dataNum += 1
join lodDataList (lodDataList[dataNum].GetGroupList())
if (dataNum > selItemCount) do
(
local findNum = findItem lodNodeTagIdxs lodDataList[dataNum].tagIdx
if (findNum != 0) do
(
lodNodes[findNum] = undefined
)
)
)
lodDataList = undefined
local newLodNodes = for item in lodNodes where (item != undefined) collect item
lodNodes.count = 0
join lodNodes newLodNodes
)
-- Make sure slod/lod model/values are up-to-date:
local objRefDefs = #()
for lodNode in lodNodes do
(
join objRefDefs (for obj in ((lodNode.GetData()).getObjs()) where (obj.refDef != undefined) collect obj.refDef)
)
objRefDefs = makeUniqueArray objRefDefs
local objSlodRefs = #()
for ref in objRefDefs do
(
for slodList in ref.slodRefs do
(
join objSlodRefs slodList
)
)
-- Make sure SLOD-source data is loaded and up-to-date:
loadSlodData objRefDefs forceLoad:True
objRefDefs = undefined
local tintsToProcess = #()
-- Make sure relevant channels are active on source-meshes:
for slodRef in objSlodRefs do
(
case of
(
(slodRef.tintMeshObjs != undefined):
(
for tintMeshObj in slodRef.tintMeshObjs do
(
activateChannels tintMeshObj.mesh
)
-- Add unprocessed tint-meshes to list:
if not slodRef.comboLod.tintingDone do
(
append tintsToProcess slodRef
slodRef.comboLod.tintingDone = True
)
)
(slodRef.meshObj != undefined):
(
activateChannels slodRef.meshObj.mesh
)
)
)
objSlodRefs = undefined
if (tintsToProcess.count != 0) do
(
gRsComboLodTint.applyLodTints tintsToProcess
)
tintsToProcess = undefined
-- Deal with model-load cancellations:
keepGoing = RSrefAllowUpdate
if not keepGoing do
(
popPrompt()
if doEndFunc do
(
GenerateLods_End()
)
return False
)
-- Get valid objects for all sub-blocks/groups:
local nodeObjItems = #()
for lodNode in lodNodes do
(
join nodeObjItems (for item in ((lodNode.GetData()).getObjs info:true lodFilter:false) where (item.canLod and (isValidNode item.node)) collect item)
)
-- Make sure that object-data is up-to-date:
for item in nodeObjItems do
item.UpdateVals()
-- Lod-Levels to be built during this generation session:
local buildLodLevels = #()
local maxLodNum = gRsLodCombinerVals.lodSuffixes.count
for item in lodNodes do
(
local lodLevel = (item.GetData()).genLodLevel
if (lodLevel == 0) do (lodLevel = maxLodNum)
append buildLodLevels maxLodNum
)
buildLodLevels = makeUniqueArray buildLodLevels
if doChildren do
(
-- Fill out array up to include the rest of the lod-level numbers:
buildLodLevels = if (buildLodLevels.count == 0) then #{} else #{1..buildLodLevels[buildLodLevels.count]}
)
-- Work out how many progress-increments are required.
-- Each object calls two progress-ticks per lod-object it's used on.
progressVal = 0
progressMax = 0
for objItem in nodeObjItems do
(
local objLodLevels = objItem.sourceLodLevels
-- Add progress-increments for each lod this prop will be combined into:
for lodLevel in buildLodLevels where (objLodLevels >= lodLevel) do
(
-- Each object has a progress-tick for placing and attaching:
progressMax += 2
)
)
nodeObjItems = undefined
popPrompt()
local progressText = "Build Combo-LODs:"
if force do (progressText = "Force-" + progressText)
------------------------------------------------------------------
-- Recursive generation step for individual lod-nodes:
------------------------------------------------------------------
progressStart progressText
keepGoing = GenerateLods_Rec lodNodes:lodNodes force:force doChildren:doChildren hidePropsAfter:hidePropsAfter
progressEnd()
------------------------------------------------------------------
-- Generation completed, now time to clean up
------------------------------------------------------------------
-- Update map-container hierarchies:
local mapNodes = for idx in topParentIdxs collect (toolRollout.dataTags[idx].GetData())
local genCountString = (genCount as string)
if (genCount == 0) then
(
gRsUlog.LogMessage ("No ComboLod objects were generated, everything is already in sync") context:"ComboLodder" quiet:False
-- No need to update data if nothing has changed...
)
else
(
for mapNode in mapNodes do
mapNode.UpdateVals refreshObjVals:True removeEmpty:False
-- Update lod-linking:
(
local lodObjItems = #()
for item in lodNodes do
(
local nodeLods = ((item.GetData()).getLodObjs info:True recurse:doChildren)
join lodObjItems nodeLods
)
updateLodLinks mapNodes updateLodHashes:lodObjItems
)
format "Time taken to generate: %s\n" ((timeStamp() - timeStart) / 1000.0)
)
-- Store Memory Residents xml-files for map-nodes:
if doMetaExport do
(
for mapNode in mapNodes do
(
mapNode.storeResidents()
)
)
-- Clear this list, not needed any more:
mapNodes = undefined
gRsPerforce.postExportAdd exclusive:false silent:false queue:xmlAddQueue
if (hideObjs.count != 0) do
(
hideObjs = makeUniqueArray hideObjs
hide hideObjs
)
-- Restore callbacks, clear values:
if doEndFunc do
(
GenerateLods_End()
)
)
(
local logMsg = "[/GenerateLods]"
gRsUlog.LogMessage (logMsg) context:"ComboLodder"
gRsULog.Validate()
)
-- Garbage-collect, clear undo:
gc()
return keepGoing
),
------------------------------------------------------------------
-- Generate mesh for attaching into ContainerLod proxies.
-- Large elements from Lods associated with a particular
-- proxy are combined into a single mesh.
------------------------------------------------------------------
fn GenContLodMeshes dataTags sizeLimit:0 =
(
local dataPerMap = for item in dataTags where (item.type == #container) collect
(
-- ContainerLod proxy-objects:
local contLodObjs = #()
-- Lod-nodes with meshes to be added to those ContainerLods:
local contLodNodeLists = #()
for childTagIdx in item.childTagIdxs do
(
local childItem = dataTags[childTagIdx]
local grpData = childItem.GetData()
local contLodObj
if (grpData.containerLodParent != undefined) do
(
contLodObj = grpData.containerLodParent.node
)
if (isValidNode contLodObj) do
(
local useItems = #()
-- We're using topmost slods that actually generate meshes (sourceHash != 0)
if ((grpData.getSourceHash getFresh:true) != 0) then
(
-- Add top groups if they generate meshes:
append useItems childItem
)
else
(
-- Otherwise, use its child-lod nodes:
for subChildIdx in childItem.childTagIdxs do
(
local subChildItem = dataTags[subChildIdx]
local subChildData = subChildItem.GetData()
if ((subChildData.getSourceHash getFresh:False) != 0) then
(
append useItems subChildItem
)
)
)
if (useItems.count != 0) do
(
local contLodNum = findItem contLodObjs contLodObj
if (contLodNum == 0) do
(
append contLodObjs contLodObj
append contLodNodeLists #()
contLodNum = contLodObjs.count
)
join contLodNodeLists[contLodNum] useItems
)
)
)
local contLodData = dataPair contLodObjs:contLodObjs contLodNodeLists:contLodNodeLists
local retVal = dataPair mapName:item.nameText data:contLodData
retVal
)
-- Make sure relevant LOD-meshes are up-to-date:
(
local genLodItems = #()
for item in dataPerMap do
(
for lodNodeList in item.data.contLodNodeLists do
(
join genLodItems lodNodeList
)
)
local success = this.GenerateLods doMetaExport:False doEndFunc:False lodNodes:genLodItems force:False doChildren:False
-- Update node-statuses in treeView:
for item in genLodItems do
(
item.updateStatus()
)
-- Return False if generateLods was cancelled/failed:
if not success do
(
return False
)
)
local newObjs = #()
for mapNum = 1 to dataPerMap.count do
(
local mapItem = dataPerMap[mapNum]
local mapName = mapItem.mapName
local contLodObjs = mapItem.data.contLodObjs
local contLodNodeLists = mapItem.data.contLodNodeLists
mapItem = dataPerMap[mapNum] = undefined
for contLodNum = 1 to contLodObjs.count do
(
local contLodName = contLodObjs[contLodNum].objectName
local newObjName = (mapName + "-" + contLodName + "_ContLodFaces")
-- Delete existing same-named mesh:
local delObj = (getNodeByName newObjName)
if (isValidNode delObj) do (delete delObj)
-- Get generated lod-objects:
local lodItems = contLodNodeLists[contLodNum]
local sourceLodObjs = #()
for item in lodItems do
(
join sourceLodObjs ((item.GetData()).getLodObjs recurse:False)
)
lodItems.count = 0
-- Filter out objects smaller than sizeLimit:
sourceLodObjs = for obj in sourceLodObjs collect
(
local objSize = (obj.Max - obj.Min)
local smallObj = True
for n = 1 to 3 while smallObj do
(
smallObj = (objSize[n] < sizeLimit)
)
if smallObj then dontCollect else obj
)
if (sourceLodObjs.count == 0) then
(
gRsUlog.LogMessage ("No mesh generated for containerLod (source-objects are too small) " + newObjName) context:"ComboLodder" quiet:False
)
else
(
gRsUlog.LogMessage ("Creating mesh for containerLod: " + newObjName) context:"ComboLodder" quiet:False
local newMeshObj = editable_mesh wireColor:(random black white) material:(multiMaterial()) name:newObjName
-- Set as "Dont Export"
setAttr newMeshObj idxDontExport True
-- Add initial tri to mesh, so that attaching works properly on UVs:
setMesh newMeshObj verts:#([0,0,0], [1,0,0], [1,1,0]) faces:#([1,2,3])
activateChannels newMeshObj
-- Put new object in the default layer:
(LayerManager.GetLayer 0).addNode newMeshObj
local success = True
progressStart "Attaching meshes..."
local objCount = sourceLodObjs.count
for objNum = 1 to objCount while (success = progressUpdate (100.0 * objNum / objCount)) do
(
local obj = sourceLodObjs[objNum]
meshop.attach newMeshObj obj attachMat:#MatToID deleteSourceNode:False
)
progressEnd()
if (not success) do
(
GenerateLods_End()
return False
)
-- Mark initial face for deletion:
local delFaces = #{1}
-- Find face-elements, and mark small ones for deletion:
if (sizeLimit > 0) do
(
local smallElemFaces = gRsSelectionTools.getFacesByElemSize newMeshObj sizeLimit showProgress:True
if (smallElemFaces == False) then
(
GenerateLods_End()
return False
)
else
(
delFaces += smallElemFaces
)
)
-- Remove any faces that have been marked for deletion:
if (delFaces.numberSet != 0) do
(
meshop.deleteFaces newMeshObj delFaces delIsoVerts:true
)
if (newMeshObj.numVerts == 0) then
(
delete newMeshObj
gRsUlog.LogMessage ("Skipping mesh-generation for containerLod (source-objects are too small) " + newObjName) context:"ComboLodder" quiet:False
)
else
(
-- Reset Xforms and collapse modifier:
CenterPivot newMeshObj
ResetXForm newMeshObj
convertToPoly newMeshObj
-- Collapse duplicate materials, removed unused ones:
RScreateNewMultiSubObject objs:#(newMeshObj) showProgress:false silent:true uniqueMats:true
RsCopyBillboardDimensionsToShaders #(newMeshObj) optimiseMultiMtls:True
append newObjs newMeshObj
)
)
)
)
-- Reactivate tool callbacks (whic were deactivated by generateLods)
GenerateLods_End()
clearSelection()
select newObjs
),
------------------------------------------------------------------
-- Splits prop-lists up into lists where props are a
-- specific distance apart:
------------------------------------------------------------------
fn splitObjsToIslands objInfoList useLodDist:True overrideDist:50 =
(
if useLodDist do
(
-- Make sure items' lodDistance values are up-to-date:
for item in objInfoList do (item.getLodDistance())
)
local objCount = objInfoList.count
local objsIslanded = #{}
objsIslanded.count = objCount
local islandObjLists = #()
local timeStart = timeStamp()
progressStart "Finding object-islands:"
local notCancelled = true
for objNum = 1 to objCount where not objsIslanded[objNum] while notCancelled do
(
objsIslanded[objNum] = true
local islandObjNums = #(objNum)
local islandObjIdx = 0
while (islandObjIdx < islandObjNums.count) do
(
islandObjIdx += 1
checkObjNumA = islandObjNums[islandObjIdx]
local objAInfo = objInfoList[checkObjNumA]
local objAPos = objAInfo.node.pos
local objADist = if useLodDist then objAInfo.lodDistance else overrideDist
for objBNum = 1 to objCount where not objsIslanded[objBNum] while notCancelled do
(
local objBDist = if useLodDist then objInfoList[objBNum].lodDistance else overrideDist
local maxLodDist = aMax #(objADist, objBDist)
-- See if these two objects are closer than their maximum lod-distance:
if (distance objAPos objInfoList[objBNum].node.pos < maxLodDist) do
(
notCancelled = progressUpdate (100.0 * objsIslanded.numberSet / objCount)
append islandObjNums objBNum
objsIslanded[objBNum] = true
)
)
)
append islandObjLists (for objNum in islandObjNums collect objInfoList[objNum].node)
)
progressEnd()
format "Time taken: %\n" (timeStamp() - timeStart)
return (if notCancelled then islandObjLists else #())
),
------------------------------------------------------------------
-- Split a group into separate island-groups,
-- if props are a specific distance apart:
------------------------------------------------------------------
fn splitGrpToIslands grp useLodDist:True overrideDist:50 =
(
local newGrps = #()
local islandObjLists = splitObjsToIslands (grp.getObjs info:true) useLodDist:useLodDist overrideDist:overrideDist
local parentData = (grp.GetParentData())
-- Don't continue if cancelled, or only one island was found:
if (islandObjLists.count > 1) do
(
-- Remove current group from its parent's list, and delete all LOD-objects:
grp.deleteLods recurse:true
(grp.GetGroupList()).count = 0
local oldGrpNum = findItem (parentData.GetGroupList()) grp
deleteItem (parentData.GetGroupList()) oldGrpNum
-- Create new groups:
newGrps = parentData.createGroups islandObjLists grpName:grp.groupName doRemove:false
-- Copy across old group's settings, and assign objects to LOD-blocks:
for newGrp in newGrps do
(
newGrp.blockType = grp.blockType
newGrp.blockRadius = grp.blockRadius
newGrp.ReassignBlocks()
)
)
return newGrps
),
-- Returns a suitable MapName and Prefix string for a given container-node
fn GetMapnameForContainer contNode =
(
if (not IsValidNode contNode) or (not IsKindOf contNode Container) do
return Undefined
-- Use node's name by default
local outVal = DataPair fullName:contNode.name shortName:contNode.name
-- Get content-export data-node for this container
local mapNode = Undefined
local contFilename = RsContFuncs.getContFilename contNode
if (contFilename != "") then
(
-- Find content-node matching this container's filename
mapNode = RsContentTree.GetContentNode contFilename
)
else
(
-- Find content-node matching this container's node-name
for item in RsContentTree.ContentTreeHelper.GetAllMapExportNodes() while (mapNode == Undefined) do
(
if (MatchPattern item.name pattern:contNode.name) do
mapNode = item
)
)
if (mapNode != Undefined) do
(
-- Use map-name from content-node
-- (this fixes things if Container helper has the wrong name)
outVal.fullName = mapNode.name
outVal.shortName = mapNode.name
-- Get shorter Prefix string, if supported by map's content-node
local mapPrefix = RsContentTree.GetExportPrefix mapNode
if (mapPrefix != "") do
outVal.shortName = mapPrefix
)
return outVal
)
)
gRsComboLodFuncs = RsComboLodFuncs()
--gRsComboLodFuncs.FindOutlyingProps (for dataTag in RsLodCombinerTool.dataTags where (dataTag.type == #container) collect dataTag.getdata())