2710 lines
83 KiB
Plaintext
Executable File
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())
|