----------------------------------------------------------------------------- -- 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())