----------------------------------------------------------------------------- -- Combined-LOD Generator Tool (ComboLodder) ----------------------------------------------------------------------------- -- UI (Uses structures defined in lodCombinerTool.ms) ----------------------------------------------------------------------------- callbacks.removeScripts id:#RsLodCombinerTool try (unregisterRedrawViewsCallback ::RsLodCombinerTool.viewportDraw) catch () -- DEBUG: Set to False to stop tool saving its data to the ComboLodder helper-node global RsComboLodDebug global RsComboLodDebug_AllowStore = True global RsComboLodDebug_EnableBlockPerObjectName = False try ( -- Deactivate store-function in existing rollout before closing it if (not RsComboLodDebug_AllowStore) do RsLodCombinerTool.allowStore = False DestroyDialog RsLodCombinerTool ) catch () -- Start listener-log: --openLog (RsConfigGetLogDir() + "ListenerLog_ComboLodder.txt") outputOnly:true -- Uses: filein (RsConfigGetWildwestDir() + "Script/3dsMax/Maps/lodCombinerTool_data.ms") ( local logMsg = ("Activating ComboLodder" + gRsLodCombinerVals.versionString) gRsUlog.LogMessage logMsg context:"ComboLodder" format "%\n" logMsg ) rollout RsLodCombinerTool "Combined-LOD Generator" width:980 height:775 ( -- Used for debugging, allows data-store functions to be run on close/save local allowStore = RsComboLodDebug_AllowStore local enableBlockPerObjectName = RsComboLodDebug_EnableBlockPerObjectName -- Set to False once LodUpdate has been run: local firstLodUpdate = True local minRolloutSize = [100,60] local callbacksActive = false local viewportDrawActive = false local allowViewportDraw = true local waitForLoad = true local nodeCallback local nodeCallback_needsTreeRedraw = False local nodeCallback_doViewRedraw = False local mapData = #() local dataTags = #() -- Provides change-callbacks with a searchable list of objects and their data-structs: local objsList = #() local objDataList = #() local containerLodList = #() local defaultNodeTag -- DotNet draw-bits: local FontFamily, SymbolFontFamily, OuterPen, ThinPen, DottedPen, SelectBrushes, DefaultBrush, WindowBrush, BlackBrush local StringFormat = dotNetObject "System.Drawing.StringFormat" ------------------------------------------------------------------------- -- Functions for getting current dataTags selection: ------------------------------------------------------------------------- fn ToolGetSelTags = ( for item in dataTags where item.selected collect item ) fn ToolGetSelBlockData selTags: = ( if (selTags == unsupplied) do selTags = ToolGetSelTags() for item in selTags where (item.type == #block) collect (item.GetData()) ) fn ToolGetSelGroupData selTags: = ( if (selTags == unsupplied) do selTags = ToolGetSelTags() local outList = for item in selTags where (item.type == #group) collect (item.GetData()) -- If blocks are selected, add their parent-groups to the group-selection list: --join outList (for item in (ToolGetSelBlockData selTags:selTags) collect (item.GetParentData())) return (makeUniqueArray outList) ) fn ToolGetSelMapData selTags: = ( if (selTags == unsupplied) do selTags = ToolGetSelTags() local outList = for item in selTags collect ((item.GetData()).getRootData()) return (makeUniqueArray outList) ) ------------------------------------------------------------------------- -- UI Widgets ------------------------------------------------------------------------- local ctrlPanelWidth = 270 local panelPadding = 10 local btnHeight = 24 local btnOffset = 6 local btnDiff = btnOffset * 2 local halfBtnWidth = (ctrlPanelWidth / 2) - (0.75 * btnDiff) dotNetControl treeViewCtrl "TreeView" width:(RsLodCombinerTool.width - ctrlPanelWidth - (3 * panelPadding)) Height:(RsLodCombinerTool.height - (2 * panelPadding)) pos:[panelPadding,panelPadding] ------------------------------------------------------------------------- -- Add outline-colour for refresh-button local isTreeRedrawRequired = False bitmap btnOutlineClrBmp "" visible:False height:(btnHeight + 6) width:(halfBtnWidth + 6) pos:[0,-40] dotNetControl RsBannerPanel "Panel" pos:[RsLodCombinerTool.width - ctrlPanelWidth - panelPadding, panelPadding] height:32 width:ctrlPanelWidth local bannerStruct = makeRsBanner dn_Panel:RsBannerPanel wiki:"ComboLodder" versionNum:gRsLodCombinerVals.VersionNum versionName:gRsLodCombinerVals.versionName ------------------------------------------------------------------------- groupBox grpViewport "Viewport Preview:" width:ctrlPanelWidth pos:(RsBannerPanel.pos + [0, RsBannerPanel.height + 4]) height:94 checkbox chkShowBoxesInViewport "Bounding-outlines" checked:True pos:(grpViewport.pos + [10,18]) checkbox chkShowShapesInViewport "Grid Shapes" checked:True pos:(chkShowBoxesInViewport.pos + [124,0]) radioButtons rdoShowSubtree "" columns:2 default:3 pos:(chkShowBoxesInViewport.pos + [1,19]) labels:#("All Nodes", "Selected Nodes", "Selected/Children", "Sel/Parents/Children") tooltip:"Show outlines for:\n All nodes\n Selected nodes\n Selected nodes and the immediate children\n Selected subtrees and their parents" checkBox chkShowNonLodding "Show invalid LOD-blocks" tooltip:"Show (in viewport and tree) LOD-blocks that don't contain LOD-source props" pos:(rdoShowSubtree.pos + [0, 34]) checked:true ------------------------------------------------------------------------- groupBox grpTextOptions "Text Options:" width:ctrlPanelWidth pos:(grpViewport.pos + [0, grpViewport.height]) height:58 checkbox chkShowTxtChildCounts "Child-counts" checked:False pos:(grpTextOptions.pos + [10,18]) \ tooltip:"Show number of sub-nodes per node" checkbox chkShowTxtPropCounts "Prop-counts" checked:True pos:(chkShowTxtChildCounts.pos + [94,0]) \ tooltip:"Show number of props used by node and its children" checkbox chkShowTxtLodCounts "LOD-counts" checked:False pos:(chkShowTxtPropCounts.pos + [83,0]) \ tooltip:"Show number of LOD-objects generated by node and its children" checkbox chkShowTxtLodDists "LOD-distances" checked:False pos:(chkShowTxtChildCounts.pos + [0,18]) \ tooltip:"Show LOD-distances set on node's generated LODs" checkbox chkShowTxt1to1Counts "(1:1) LODs" checked:False pos:(chkShowTxtPropCounts.pos + [0,18]) \ tooltip:"Show number of LOD-objects that will LOD only 1 prop" checkbox chkShowTxt2to1Counts "(2:1) LODs" checked:False pos:(chkShowTxtLodCounts.pos + [0,18]) \ tooltip:"Show number of LOD-objects that will LOD only 2 props" ------------------------------------------------------------------------- groupBox grpGenerate "Generate LODs:" pos:(grpTextOptions.pos + [0, grpTextOptions.height]) width:ctrlPanelWidth height:92 enabled:false button btnGenLodsAll "For All Nodes" align:#right pos:(grpGenerate.pos + [btnOffset,16]) width:100 height:btnHeight enabled:false button btnGenLodsNodeChilds "Selected Nodes & Children" pos:(btnGenLodsAll.pos + [btnOffset + 100, 0]) width:150 height:btnHeight enabled:false button btnGenLodsNode "Selected Nodes" pos:(btnGenLodsAll.pos + [0, btnHeight + 2]) width:100 height:btnHeight across:2 enabled:false button btnOutputMeta "Output Resident .meta" pos:[btnGenLodsNodeChilds.pos.x, btnGenLodsNode.pos.y] width:150 height:btnHeight enabled:false tooltip:"Output Resident Models list to metafile.\n\n(automatically done by lod-generator too)" checkBox chkForceGenerate "Force Regen" tooltip:"'Generate' will regenerate up-to-date LOD-objects too" pos:(btnGenLodsNode.pos + [1, btnHeight + 5]) align:#right enabled:false across:2 button btnAreaZoneGen "Memory-Residents Toolkit" pos:(btnOutputMeta.pos + [0, btnHeight + 2]) width:150 height:(btnHeight - 4) enabled:True tooltip:"Open Memory Resident Zones Toolkit" local generateAllCtrls = #(grpGenerate, btnGenLodsAll, chkForceGenerate, btnOutputMeta) -- These controls are enabled when any generation can be done local generateCtrls = #(btnGenLodsNodeChilds, btnGenLodsNode) -- These controls are only enabled when a generatable node is selected ------------------------------------------------------------------------- groupBox grpAddGrp "Edit Hierarchy:" pos:(grpGenerate.pos + [0, grpGenerate.height]) width:ctrlPanelWidth height:116 enabled:false button btnAddSubgroup "Add SLOD-group" tooltip:"Add new SLOD-group to currently-selected node" pos:(grpAddGrp.pos + [btnOffset, 16]) width:100 height:btnHeight enabled:false checkBox chkIncludeSel "Include Selected Props" tooltip:"Add selected props to new subgroup" pos:(btnAddSubgroup.pos + [100 + btnOffset, 0]) checked:true enabled:false button btnDelGrps "Delete Groups" tooltip:"Delete selected groups" height:btnHeight width:100 pos:(btnAddSubgroup.pos + [0, btnHeight + 2]) enabled:false button btnAddToGrp "Add Selected Props to Group" tooltip:"Add selected RsRef props to the selected group" height:btnHeight width:150 pos:(btnDelGrps.pos + [btnOffset + 100, 0]) enabled:false button btnCombineGrps "Combine Groups" tooltip:"Combine selected sibling-groups into one" height:20 width:100 pos:(btnDelGrps.pos + [0, btnHeight + 2]) enabled:false button btnSplitToIslands "Split Group Islands:" tooltip:"Split selected group up into groups with props no closer than a certain distance" \ height:20 width:150 pos:[btnAddToGrp.pos.x, btnCombineGrps.pos.y] enabled:false radiobuttons rdoIslandDist "" labels:#("Prop LOD-Dists", "") offset:[-52,0] align:#right default:2 enabled:false spinner spnIslandDist "" range:[20,10000, 40] width:50 type:#integer align:#right pos:(rdoIslandDist.pos + [115,-1]) enabled:false local grpCtrls = #(grpAddGrp, btnAddSubgroup, chkIncludeSel, btnDelGrps, btnCombineGrps, btnAddToGrp) local addGrpCtrls = #(btnAddSubgroup, chkIncludeSel) local islandCtrls = #(btnSplitToIslands, rdoIslandDist, spnIslandDist) -- True if obj is is same container as selected-group's container: fn containerLodFilt obj = ( (RsIsContainerLod obj) and ( local selGrpData = (ToolGetSelGroupData())[1].getRootData() selGrpData.contNode == (Containers.IsInContainer obj) ) ) ------------------------------------------------------------------------- groupBox grpBlocking "SLOD-group Params:" pos:(grpAddGrp.pos + [0, grpAddGrp.height]) width:ctrlPanelWidth height:192 editText txtRename "Group Name:" width:240 align:#left pos:(grpBlocking.pos + [20, 16]) enabled:false label lblContLodParent "ContainerLod:" align:#left across:7 enabled:false pos:[grpBlocking.pos.x + 15, txtRename.pos.y + 24] enabled:false pickButton btnContLodParent "" filter:containerLodFilt autoDisplay:true pos:(lblContLodParent.pos + [71,-3]) width:174 height:21 enabled:false label lblBlockType "Blocking Type:" align:#left across:7 enabled:false pos:[grpBlocking.pos.x + 13, txtRename.pos.y + 48] dropdownList lstBlockType "" items:(gRsLodCombinerVals.blockTypeNames) enabled:false width:100 labelOnTop:false pos:(lblBlockType.pos + [73,-3]) selection:0 spinner spnBlockSize "Blocking Radius:" range:[20, 1500, 50] type:#integer width:140 pos:(txtRename.pos + [-76,70]) indeterminate:true enabled:false label lblBlockPos "Child-block origin:" pos:[grpBlocking.pos.x + 4, spnBlockSize.pos.y + 20] enabled:false spinner spnPosX "X:" range:[-9999999, 9999999, 0] width:80 pos:(lblBlockPos.pos + [88,0]) enabled:false indeterminate:true spinner spnPosY "Y:" range:[-9999999, 9999999, 0] width:80 pos:(spnPosX.pos + [24,0]) enabled:false indeterminate:true checkBox chkBlockByProp "Block by Objectname" enabled:false pos:(lblBlockPos.pos + [4,22]) tristate:2 across:2 \ tooltip:"Each LOD-block will only contain props with the same objectname" button btnResetPos "^ Reset Pos ^" align:#right offset:[-40,-1] height:18 enabled:false checkBox chkDoResidents "Memory Resident models" tooltip:"" enabled:false pos:(chkBlockByProp.pos + [0, 16]) tristate:2 button btnReblock "Recalculate sub-blocks" height:btnHeight width:(ctrlPanelWidth - btnDiff) pos:[grpBlocking.pos.x + btnOffset, chkBlockByProp.pos.y + 33] enabled:false \ tooltip:"Recalculate this SLOD-group's LOD-blocks" -- These controls are only enabled when a Group node is selected: local blockSettingCtrls = #(grpBlocking, txtRename, lblContLodParent, btnContLodParent, lblBlockType, lstBlockType, spnBlockSize, lblBlockPos, spnPosX, spnPosY, btnResetPos, chkBlockByProp, chkDoResidents, btnReblock) local blockingCtrls = #(lblBlockType, lstBlockType, spnBlockSize, lblBlockPos, spnPosX, spnPosY, btnResetPos, chkBlockByProp, btnReblock) ------------------------------------------------------------------------- groupBox grpBoxB "" width:ctrlPanelWidth height:100 pos:(grpBlocking.pos + [0, grpBlocking.height]) button btnFindSelInTree "Find selected Props/LODs in Tree" width:(ctrlPanelWidth - btnDiff) height:btnHeight pos:(grpBoxB.pos + [btnOffset,13]) tooltip:"Select nodes for selected objects, if they're in the ComboLodder tree" button btnRemSelFromGrps "Remove selected Props from Tree" width:(ctrlPanelWidth - btnDiff) height:btnHeight pos:(btnFindSelInTree.pos + [0, btnHeight + 3]) tooltip:"Remove selected objects from the ComboLodder tree" button btnRefresh "Refresh Tree" width:halfBtnWidth height:(btnHeight) pos:(btnRemSelFromGrps.pos + [0, btnHeight + 5]) across:2 checkbutton btnDebug "[Debug Mode]" width:halfBtnWidth height:(btnHeight) pos:(btnRefresh.pos + [halfBtnWidth + (0.5 * btnDiff), 0]) checked:RsComboLodDebug ------------------------------------------------------------------------- groupBox grpContLodder "ContainerLod Mesh Generation:" width:ctrlPanelWidth height:73 pos:(grpBoxB.pos + [0, grpBoxB.height]) button btnGenContLodMeshes "Generate ContainerLod Meshes" width:((ctrlPanelWidth - btnDiff) - 70) height:btnHeight pos:(grpContLodder.pos + [btnOffset, 16]) tooltip:"Generate large-element meshes for nodes with associated ContainerLod proxies, for attaching into ContainerLod source-objects" spinner spnMinBBsize "" range:[0,50,5] type:#float align:#right width:66 offset:[-4,-22] tooltip:"Set bounding-box dimension-limit for small face-elements" label lblMinBBsize "Min Elem Size:" pos:(spnMinBBsize.pos + [-53, -15]) button btnOptBBs "Optimise Billboard Materials" width:(ctrlPanelWidth - btnDiff) height:btnHeight pos:(btnGenContLodMeshes.pos + [0, btnHeight + 3]) tooltip:"Optimise Billboard materials on selected objects" ------------------------------------------------------------------------- -- Functions ------------------------------------------------------------------------- -- Show outline-colour around Redraw Tree button fn SetRedrawRequired inVal = ( if (not inVal) then ( btnRefresh.text = "Refresh Tree" btnOutlineClrBmp.visible = False ) else ( if (btnOutlineClrBmp.bitmap == Undefined) do ( btnOutlineClrBmp.pos = btnRefresh.pos - [3,3] btnOutlineClrBmp.bitmap = Bitmap btnOutlineClrBmp.width btnOutlineClrBmp.height color:Red ) if (not isTreeRedrawRequired) do ( btnRefresh.text = "!!! UPDATE TREE !!!" btnOutlineClrBmp.visible = False btnOutlineClrBmp.visible = True btnOutlineClr = inVal -- Ensure that pressed ctrl is drawn over outline-box btnRefresh.visible = False btnRefresh.visible = True ) ) -- Set flag to indicate current state isTreeRedrawRequired = inVal return OK ) -- Returns True if Map Export is active: fn isMapExportActive = ( (::RsMapRoll != undefined) and (::RsMapRoll.open) and (::RsMapRoll.workingStatus == #working) ) -- Called by RedrawViewsCallback, draws whatever's in the rollout's draw-queue: fn viewportDraw = ( if (not callbacksActive) or RSmaxfilingNow or RsRefsWaitingForRedrawLoad then ( -- Skip viewport-draw if RsRef load is underway: return false ) else ( -- Refresh viewport after RsRef load completes: if waitForLoad do ( waitForLoad = false RsLodCombinerTool.RefreshTreeView doGC:false ) ) -- local timeStart = timeStamp() -- Only continue drawing if editor-tool is still open: if not ( ::RsLodCombinerTool.open ) do ( unregisterRedrawViewsCallback ::RsLodCombinerTool.viewportDraw return OK ) -- Stop processing if viewport-draw is deactivated in rollout, or map is being exported: if (not allowViewportDraw) or (isMapExportActive()) do return OK viewportDrawActive = true gw.setTransform (Matrix3 1) local showBoxes = chkShowBoxesInViewport.checked local showShapes = chkShowShapesInViewport.checked for item in mapData do ( item.viewportDraw showBoxes:showBoxes showShapes:showShapes ) --format "Draw-Time: % ms\n" (timestamp() - timeStart) ) fn UpdateMapData = ( -- Make sure slod/lod model/values are loaded: local objRefDefs = for obj in geometry where (isProperty obj #refDef) collect obj.refDef gRsComboLodFuncs.loadSlodData objRefDefs --if (keyboard.escPressed) do return false pushPrompt "Reading ComboLodder map-data" -- Compile lookup-list for existing data: local mapDataNames = for item in mapData collect (toLower item.mapName) -- Get data per container: local cancelled = False local newMapData = #() for cont in rootnode.children where (isKindOf cont Container) and (cont.children.count != 0) do ( -- Get MapName for this container local nameItem = gRsComboLodFuncs.GetMapnameForContainer cont local mapName = nameItem.shortName local findNum = findItem mapDataNames (toLower mapName) if (findNum == 0) then ( -- Create new RsLodCombinerMap struct, with dataNode set if found: local dataNode = undefined for obj in cont.children where (isKindOf obj RsGenericDataNode) and (obj.dataClass == "ComboLodder") while (dataNode == undefined) do ( dataNode = obj ) local newData = RsLodCombinerMap mapName:nameItem.shortName fullMapName:nameItem.fullName contNode:cont dataNode:dataNode newData.Restore cancelled:&cancelled Append newMapData newData ) else ( -- Use pre-loaded data, if available: Append newMapData mapData[findNum] ) ) popPrompt() if cancelled then ( allowStore = False destroyDialog RsLodCombinerTool return False ) else ( mapData = newMapData return True ) ) fn Init = ( -- Initialise tech-art banner: bannerStruct.setup() -- Set minimum rollout-width: minRolloutSize.X = grpGenerate.width + 60 -- Initialise TreeView control bits: treeViewCtrl.BackColor = gRsLodCombinerVals.windowColour treeViewCtrl.DrawMode = treeViewCtrl.drawMode.ownerDrawAll treeViewCtrl.ItemHeight = gRsLodCombinerVals.nodeItemHeight treeViewCtrl.Sorted = true WindowBrush = gRsLodCombinerVals.WindowBrush DefaultBrush = gRsLodCombinerVals.DefaultBrushes.normal BlackBrush = gRsLodCombinerVals.blackBrush -- Set up font bits: FontFamily = treeViewCtrl.Font.FontFamily OuterPen = gRsLodCombinerVals.OuterPen ThinPen = gRsLodCombinerVals.ThinPen DottedPen = gRsLodCombinerVals.DottedPen SelectBrushes = gRsLodCombinerVals.SelectBrushes SymbolFontFamily = dotNetObject "System.Drawing.FontFamily" "Wingdings" ------------------------ -- Initial data-load -- ------------------------ -- Kill tool if Rsref database isn't/can't be loaded: local success = RsRefFuncs.DatabaseActive toolName:"ComboLodder" if (not success) do return False -- Convert slodref arrays to arrays of arrays, if using old rsref script: ( local sloddedRefs = for ref in RsRefData.refs where (ref.slodRefs.count != 0) collect ref if (sloddedRefs.count != 0) do ( if not (isKindOf sloddedRefs[1].slodRefs[1] array) do ( format "Converting SlodRef arrays from older RsRef script...\n" for ref in sloddedRefs do ( ref.slodRefs = for item in ref.slodRefs collect #(item) ) ) ) ) ( -- Make sure slod/lod model/values are loaded: local objRefDefs = for obj in geometry where (isProperty obj #refDef) collect obj.refDef success = gRsComboLodFuncs.loadSlodData objRefDefs forceLoad:False ) if (not success) do (return False) success = updateMapData() if (not success) do (return False) -- Remove unused data-nodes: local usedHelpers = for item in mapData collect item.dataNode delete (for obj in helpers where (isKindOf obj RsGenericDataNode) and (obj.dataClass == "ComboLodder") and (findItem usedHelpers obj == 0) collect obj) callbacksActive = true return True ) -- These are set to booleans, which are picked up by RCMenu too: local isSingleSel local isSiblings local isContainer local isGroup local isBlock local isObject local canAddGrp local canCombine -- Update block-param controls to match current selection: fn updateCtrls = ( pushPrompt "Updating controls" -- Enable btnGenContLodMeshes if any groups have associated containerLod proxies: btnGenContLodMeshes.enabled = False for item in dataTags while (not btnGenContLodMeshes.enabled) do ( btnGenContLodMeshes.enabled = ((item.type == #group) and ((item.GetData()).containerLodParent != undefined)) ) lblMinBBsize.enabled = spnMinBBsize.enabled = btnGenContLodMeshes.enabled local selTags = ToolGetSelTags() local selTypes = makeUniqueArray (for item in selTags collect item.type) local selType = if (selTypes.count == 1) then selTypes[1] else #multi isSingleSel = (selTags.count == 1) isContainer = false isGroup = false isBlock = false isObject = false case selType of ( #object:(isObject = true) #block:(isBlock = true) #group:(isGroup = true) #container:(isContainer = true) ) local selParents = makeUniqueArray (for item in selTags collect (if (item.GetData() == undefined) then undefined else ((item.GetData()).GetParentData()))) isSiblings = (selParents.count == 1) selParents = undefined -- See if any generatable nodes are available: local canGenItemsCount = 0 for item in dataTags where (isProperty (item.GetData()) #lodSynced) do ( canGenItemsCount += 1 ) local canGenerate = (canGenItemsCount != 0) generateAllCtrls.enabled = canGenerate if canGenerate then ( -- See if selection contains generatable nodes: local selItems = for item in selTags where (isProperty (item.GetData()) #lodSynced) collect (item.GetData()) generateCtrls.enabled = (selItems.count != 0) -- Disable recursive-generate button for block-node selection: local nonBlockItems = for item in selItems where (isProperty item #GetGroupList) collect item btnGenLodsNodeChilds.enabled = (nonBlockItems.count != 0) -- Disable node-generate button if selected nodes are greyed out: local selGenItems = for dataItem in selItems where (((dataItem.GetParentData()) != undefined) and (dataItem.lodSynced != undefined)) collect item btnGenLodsNode.enabled = (selGenItems.count != 0) ) else ( generateCtrls.enabled = false ) local isContainerWithChilds = False if isContainer do ( for item in selTags where (item.type == #Container) while not isContainerWithChilds do ( if (((item.GetData()).GetGroupList()).count != 0) do ( isContainerWithChilds = True ) ) ) btnGenLodsNodeChilds.enabled = (btnGenLodsNodeChilds.enabled or isContainerWithChilds) ------------------------------------------------------------------------- -- Set up Edit Hierarchy control-group: if (selTags.count == 0) then (grpCtrls.enabled = false) else ( grpCtrls.enabled = isSingleSel and (isContainer or isGroup) btnDelGrps.enabled = isGroup canAddGrp = isSingleSel and isContainer canAddToGrp = isSingleSel and isGroup addGrpCtrls.enabled = canAddGrp btnAddToGrp.enabled = canAddToGrp canCombine = not isSingleSel and isGroup and isSiblings btnCombineGrps.enabled = canCombine btnAddToGrp.enabled = isGroup ) ------------------------------------------------------------------------- -- Set up Group Params control-group: -- Initially set ctrls as indeterminate: txtRename.text = "" lstBlockType.selection = 0 spnBlockSize.indeterminate = true spnPosX.indeterminate = true spnPosY.indeterminate = true chkBlockByProp.tristate = 2 chkDoResidents.tristate = 2 btnContLodParent.object = undefined btnContLodParent.text = "NOT SET" -- Get list of selected groups (and groups with lod-block selections) local selGrpData = ToolGetSelGroupData selTags:selTags local selMapData = ToolGetSelMapData selTags:selTags selTags = undefined --btnDelGrps.enabled = (selGrpData.count != 0) if (selGrpData.count == 0) then ( blockSettingCtrls.enabled = False islandCtrls.enabled = False ) else ( blockSettingCtrls.enabled = True -- The 'Block by Objectname' control is only enabled if flag has been set chkBlockByProp.enabled = enableBlockPerObjectName -- Only bother showing re-blocker button if selected groups contain sub-blocks: local hasSubGroups = false for item in selGrpData while (not hasSubGroups) do ( if (item.genLodLevel > 1) and ((item.GetGroupList()).count != 0) do hasSubGroups = true ) btnReblock.enabled = hasSubGroups islandCtrls.enabled = hasSubGroups spnIslandDist.enabled = spnIslandDist.enabled and (rdoIslandDist.state == 2) local namesList = makeUniqueArray (for item in selGrpData collect item.groupName) local typeList = makeUniqueArray (for item in selGrpData collect item.childBlockType) local radiusList = makeUniqueArray (for item in selGrpData where (item.childBlockType != #none) and (item.childBlockRadius != undefined) collect item.childBlockRadius) local posXList = makeUniqueArray (for item in selGrpData where (item.childBlockPos != undefined) collect item.childBlockPos.X) local posYList = makeUniqueArray (for item in selGrpData where (item.childBlockPos != undefined) collect item.childBlockPos.Y) local blockByPropList = makeUniqueArray (for item in selGrpData collect item.blockByProp) local topGrps = makeUniqueArray (for item in selGrpData collect (local ItemParentData = Item.GetParentData(); if (isKindOf ItemParentData RsLodCombinerGroup) then ItemParentData else item)) local contLodList = makeUniqueArray (for item in topGrps collect (if (item.containerLodParent != undefined) and (isValidNode item.containerLodParent.node) then item.containerLodParent.node else undefined)) topGrps = undefined -- Send unique values to controls: if (namesList.count == 1) and (namesList[1] != undefined) do txtRename.text = namesList[1] if (typeList.count == 1) do lstBlockType.selection = findItem gRsLodCombinerVals.blockTypes typeList[1] if (radiusList.count == 1) do spnBlockSize.value = radiusList[1] if (posXList.count == 1) do spnPosX.value = posXList[1] if (posYList.count == 1) do spnPosY.value = posYList[1] if (blockByPropList.count == 1) do chkBlockByProp.checked = blockByPropList[1] case contLodList.count of ( 1:(btnContLodParent.object = contLodList[1]) 0:() Default:(btnContLodParent.text = "Multiple") ) ) if (selMapData.count != 0) do ( -- Enable this tickybox for container/group nodes: chkDoResidents.enabled = True grpBlocking.enabled = True local doResidentsList = makeUniqueArray (for item in selMapData collect (item.doResidents)) if (doResidentsList.count == 1) do chkDoResidents.checked = doResidentsList[1] ) -- Set Group box to enabled if any of its associated controls are enabled: local anyEnabled = false for ctrl in grpCtrls while not anyEnabled where ctrl.enabled do (anyEnabled = true) grpAddGrp.enabled = anyEnabled popPrompt() return OK ) -- Hide groups that only contain hidden props: fn setViewportHidden = ( -- (dataTags array is handily ordered bottom-up) for tagItem in dataTags do ( case tagItem.type of ( #object: ( local obj = (tagItem.GetData()).node tagItem.isHidden = (not isValidNode obj) or (obj.isHidden) ) Default: ( local isHidden = False if (tagItem.childTagIdxs.numberSet != 0) do ( isHidden = True for tagNum in tagItem.childTagIdxs while isHidden do ( isHidden = dataTags[tagNum].isHidden ) ) tagItem.isHidden = isHidden ) ) ) ) -- Set viewport-visibility for nodes: fn SetViewportShow = ( local showBoxes = chkShowBoxesInViewport.checked local showShapes = chkShowShapesInViewport.checked local showAll = (rdoShowSubtree.state == 1) local showJustNodes = (rdoShowSubtree.state == 2) local showNodesNChilds = (rdoShowSubtree.state == 3) local showTrees = (rdoShowSubtree.state == 4) -- Deactivate viewport-draw if nothing will be visible: allowViewportDraw = (showBoxes or showShapes) if not allowViewportDraw do ( completeRedraw() return false ) pushPrompt "Setting block-outline visibility" local selTags = for item in dataTags where item.selected collect item if showAll then ( -- Draw all nodes in viewport: dataTags.viewShow = True ) else ( -- Draw only subtree/parents in viewport: dataTags.viewShow = False local childIdxList = #{} local objParentIdxs = #{} for item in selTags do ( item.viewShow = True case item.type of ( #object: ( local parentData = (item.GetData()).GetParentData() if (parentData != undefined) and (parentData.tagIdx != undefined) do ( objParentIdxs[parentData.tagIdx] = True ) ) #container: ( -- Force to show child-node data instead if a container is selected: showJustNodes = False showNodesNChilds = True showTrees = False ) ) if showTrees do ( -- Set parents as visible: local thisTag = item while (thisTag != undefined) do ( thisTag.viewShow = True local parentData = (thisTag.GetData()).GetParentData() thisTag = if (parentData == undefined) or (parentData.tagIdx == undefined) then undefined else dataTags[parentData.tagIdx] ) ) childIdxList += item.childTagIdxs ) (for idx in objParentIdxs collect dataTags[idx]).viewShow = True -- Set all child-node tags as visible: if (not showJustNodes) do ( local childIdxList = (childIdxList as array) local childNum = 0 while (childNum < childIdxList.count) do ( childNum += 1 local childTag = dataTags[childIdxList[childNum]] childTag.viewShow = True if (not showNodesNChilds) do ( join childIdxList (childTag.childTagIdxs as array) ) ) ) ) selTags = undefined --setViewportHidden() popPrompt() completeRedraw() ) -- Set index for closest non-gridded parent-node: fn recSetGridParentLists thisNode gridParentIdxList:#() = ( for n = 0 to (thisNode.Nodes.count - 1) do ( local childNode = thisNode.Nodes.Item[n] if (childNode.Tag != undefined) do ( local tagVal = childNode.Tag.Value local tagData = tagVal.GetData() -- Make dereferenced list, to avoid back-adding: local newIdxList = #() join newIdxList gridParentIdxList if (isProperty tagData #gridParentIdxList) do ( -- Add idx to list if this is a gridded group-node: if (tagVal.type != #object) and ((tagData.blockType != #none) or (tagData.childBlockType != #none)) do ( append newIdxList tagData.tagIdx ) -- Set group/object data-struct's idx: tagData.gridParentIdxList = newIdxList ) recSetGridParentLists childNode gridParentIdxList:newIdxList ) ) ) -- Recursively adds node-tags to dataTag array, setting up tagIdx values in order they appear in tree: fn recAddNodesToList thisNode firstNode:true = ( local childTagIdxs = for n = 0 to (thisNode.Nodes.count - 1) collect ( local childNode = thisNode.Nodes.Item[n] local childTag = recAddNodesToList childNode firstNode:false nonGridParentIdx:nonGridParentIdx childTag.tagIdx ) local thisNodeTag if firstNode then ( -- Recursively set up gridParentIdxList values for rootnode children: recSetGridParentLists thisNode ) else ( -- Collect dataTag, and set its tagIdx value: thisNodeTag = thisNode.Tag.Value thisNodeTag.childTagIdxs = (childTagIdxs as bitArray) append dataTags thisNodeTag thisNodeTag.tagIdx = dataTags.count local tagData = thisNodeTag.GetData() if (tagData != undefined) do ( -- Set datatag idx: tagData.tagIdx = thisNodeTag.tagIdx -- Set to node - recursion won't have given this node an idx yet: nonGridParentIdx = nonGridParent ) ) -- Returned value is used by recursion: return thisNodeTag ) -- Clear dataTags from all nodes, to be sure the data is being taken out of circulation: fn clearTreeData = ( lastSelNodeTag = undefined local nodeNum = 0 local allNodes = #(treeViewCtrl) while (nodeNum < allNodes.count) do ( nodeNum += 1 local thisNode = allNodes[nodeNum] local childNodes = thisNode.nodes for idx = 0 to (childNodes.count - 1) do ( append allNodes childNodes.Item[idx] ) if (thisNode.Tag != undefined) and (isProperty thisNode.Tag.Value #GetData) do ( thisNode.Tag.Value.SetData undefined ) thisNode.Tag = undefined ) dataTags.count = 0 treeViewCtrl.nodes.clear() -- Dotnet garbage-collect: (dotnetClass "System.GC").Collect() (dotnetClass "System.GC").WaitForPendingFinalizers() (dotnetClass "System.GC").Collect() ) -- Updates node-status text/colours, with options applied: fn UpdateNodeStatus thisNode updateObjVals:True refreshObjVals:False = ( thisNode.updateStatus updateObjVals:updateObjVals refreshObjVals:refreshObjVals \ showLodDists:chkShowTxtLodDists.checked \ showPropCounts:chkShowTxtPropCounts.checked \ showLodCounts:chkShowTxtLodCounts.checked \ showChildCounts:chkShowTxtChildCounts.checked \ show1to1Counts:chkShowTxt1to1Counts.checked \ show2to1Counts:chkShowTxt2to1Counts.checked ) -- Update the status-text on all nodes, after status-options have changed: fn updateAllNodeStatus = ( setWaitCursor() for thisNode in dataTags do ( -- Don't update object values, nothing should have changed UpdateNodeStatus thisNode updateObjVals:False ) treeViewCtrl.invalidate() setArrowCursor() return OK ) on chkShowTxtLodDists changed state do (updateAllNodeStatus()) on chkShowTxtPropCounts changed state do (updateAllNodeStatus()) on chkShowTxtLodCounts changed state do (updateAllNodeStatus()) on chkShowTxtChildCounts changed state do (updateAllNodeStatus()) on chkShowTxt1to1Counts changed state do (updateAllNodeStatus()) on chkShowTxt2to1Counts changed state do (updateAllNodeStatus()) local lastSelNodeTag fn RefreshTreeView selItems: updateObjVals:True refreshObjVals:False doGC:True doLodLinks:True = ( if waitForLoad or (isMapExportActive()) do return false local timeStart = timeStamp() -- Viewport-callback will re-enable refreshing when RsRef system has finished loading: if (RSmaxfilingNow or RsRefsWaitingForRedrawLoad) do ( waitForLoad = true return false ) callbacksActive = false -- Collect data-structs for currently-selected nodes, so that they can be re-selected later: local currentSelData = if (selItems != unsupplied) then selItems else (for item in ToolGetSelTags() collect (item.GetData())) -- Collect objects too, as datastructs may change: local currentSelObjs = for item in currentSelData where (isProperty item #node) collect item.node -- Clear existing tree-data: clearTreeData() -- Don't continue if this fails - the tool is about to shut down. local updateSuccess = updateMapData() if (not updateSuccess) do return false pushPrompt "Refreshing tree-view..." setWaitCursor() dataTags = #() objsList = #() objDataList = #() containerLodList = #() local showNonLodding = chkShowNonLodding.state if (mapData.count == 0) then ( local dummyNode = treeViewCtrl.Nodes.Add "" local dummyTag = RsLodCombinerNodeTag nameText:"No containers found" UpdateNodeStatus dummyTag dummyNode.tag = dotNetMXSValue dummyTag ) else ( for contData in mapData do ( local mapName = (contData.mapName as string) pushPrompt ("Adding nodes to tree: " + mapName) local contNode = treeViewCtrl.Nodes.Add mapName local contTag = RsLodCombinerNodeTag nameText:mapName type:#container treeNode:contNode contTag.SetData ContData contNode.tag = dotNetMXSValue contTag pushPrompt "Updating node-values..." -- Initialises data-helpers if needed, unpacking from paramblock: updateNodeStatus contTag updateObjVals:updateObjVals refreshObjVals:refreshObjVals popPrompt() local dataList = for item in (contData.GetGroupList()) collect item local nodeParents = for n = 1 to dataList.count collect contNode dataList.tagIdx = undefined local dataNum = 0 local lodBlockNum = 0 local grpParent while (dataNum < dataList.count) do ( dataNum += 1 local dataItem = dataList[dataNum] -- Trigger rebuild of child-groups if edits have been made -- e.g. if group boundaries have been changed if dataItem.reblockRequired do dataItem.ReassignBlocks() local isLodBlock = (dataItem.genLodLevel == 1) local canLod = (dataItem.lodSynced != undefined) if (canLod) or (not isLodBlock) or (showNonLodding) do ( local nodeType = if isLodBlock then #block else #group local nodeName = Undefined if isLodBlock do ( local ItemParentData = dataItem.GetParentData() if (ItemParentData != grpParent) do ( grpParent = ItemParentData lodBlockNum = 0 ) dataItem.childNum = (lodBlockNum += 1) ) -- Collect containerLod objects as we go: local contLodItem = dataItem.containerLodParent if (contLodItem != undefined) do ( appendIfUnique containerLodList contLodItem contLodItem = undefined ) local parentNode = nodeParents[dataNum] local groupNode = parentNode.Nodes.Add "" local groupTag = RsLodCombinerNodeTag type:nodeType treeNode:groupNode groupTag.SetData dataItem updateNodeStatus groupTag updateObjVals:updateObjVals groupNode.tag = dotNetMXSValue groupTag (dataItem.GetObjItems()).tagIdx = undefined (dataItem.GetGroupList()).tagIdx = undefined join dataList (dataItem.GetGroupList()) join nodeParents (for n = 1 to (dataItem.GetGroupList()).count collect groupNode) -- Add object-nodes: for objItem in (dataItem.GetObjs recurse:False info:True) do ( -- Used by change-callbacks to find object-data: append objDataList objItem append objsList objItem.node local objNode = groupNode.Nodes.Add objItem.node.name local objTag = RsLodCombinerNodeTag type:#object treeNode:objNode objTag.SetData ObjItem UpdateNodeStatus objTag updateObjVals:updateObjVals objNode.tag = dotNetMXSValue objTag ) ) -- Tidy up source-array items as we go: dataList[dataNum] = undefined ) grpParent = undefined -- Add lod-object data to object-lists too: local lodObjItems = contData.getLodObjs info:true recurse:true join objDataList lodObjItems join objsList (for item in lodObjItems collect item.node) setArrowCursor() popPrompt() ) ) -- Collect flattened list of dataTags in order they appear in tree, now it's been created and sorted -- (allows shift-select system to work properly, as tagIdxs can be set) pushPrompt "Refreshing node-indices" recAddNodesToList treeViewCtrl popPrompt() format "ComboLodder: Data-refresh took %s (%MB)\n" ((timeStamp() - timeStart) / 1000.0) ((sysinfo.getMAXMemoryInfo())[3] / (1024.0 ^ 2)) pushPrompt "Redrawing tree" -- Expand nodes marked for such (non-objects, by default) for dataTag in dataTags where ((dataTag.GetData()) != undefined) and ((dataTag.GetData()).expanded) do ( dataTag.treeNode.Expand() ) -- Recreate previous node-selection, where possible, and collect its treeNodes: local selTreeNodes = for item in dataTags collect ( local ItemData = Item.GetData() if (findItem currentSelData ItemData != 0) or ((isProperty item #node) and (findItem currentSelObjs ItemData)) then ( item.selected = true item.treeNode ) else dontCollect ) currentSelData.count = 0 currentSelObjs.count = 0 -- Make first node visible: if (dataTags[1] != undefined) and (dataTags[1].treeNode != undefined) do ( dataTags[dataTags.count].treeNode.EnsureVisible() ) -- Make selected nodes visible (in reverse order) for n = selTreeNodes.count to 1 by -1 do ( selTreeNodes[n].EnsureVisible() ) selTreeNodes = undefined treeViewCtrl.invalidate() popPrompt() updateCtrls() popPrompt() -- Make sure LOD-links match the loaded tree: if doLodLinks do ( local mapNodes = for dataTag in dataTags where (dataTag.type == #container) collect (dataTag.GetData()) gRsComboLodFuncs.updateLodLinks mapNodes firstLodUpdate:firstLodUpdate firstLodUpdate = False ) -- Do garbage-collect if a change has been made: if doGC do ( pushPrompt "Post tree-generation garbage-collect..." gc light:True popPrompt() ) callbacksActive = true -- Dotnet garbage-collect: (dotnetClass "System.GC").Collect() -- Update node viewport-visibility, if required -- (redraws viewport) SetViewportShow() -- Get rid of Redraw button's red outline SetRedrawRequired False --format "ComboLodder: Total refresh took %s (%MB)\n" ((timeStamp() - timeStart) / 1000.0) ((sysinfo.getMAXMemoryInfo())[3] / (1024.0 ^ 2)) return OK ) -- Remove objects from all groups: fn ToolRemoveObjs remObjs checkAll:true = ( local removed = #{} removed.count = remObjs.count for item in mapData while checkAll or (removed.numberSet != remObjs.count) do ( local remCount = removed.numberSet item.removeObjs remObjs removed:removed checkAll:checkAll removeEmpty:False ) RefreshTreeView() ) -- Add objects to grp (removing from all other groups first) fn ToolAddToGrp grpData objs:Selection refreshView:true = ( -- (LOD-group is gridded, add to its grid-parent instead) if (grpData.gridParentIdxList.count != 0) do ( local gridParentIdx = grpData.gridParentIdxList[1] grpData = dataTags[gridParentIdx].GetData() ) -- Only add RsRef objects that are in the same container as this group: local grpCont = (grpData.getRootData()).contNode local addObjsToGrps = for obj in objs where ((Containers.IsInContainer obj) == grpCont) and (isRsRef obj) collect obj if (addObjsToGrps.count == 0) do return OK -- Warn if user is trying to add props to a SLOD Group with a different IPL Group -- This does nothing if group is empty (Undefined) or already contains a mixture (#Multiple) if (IsKindOf grpData.blockIPLName String) do ( local badIpls = #() local badIplObjs = #() for obj in addObjsToGrps do ( local objIpl = GetAttr obj idxIPLGroup if (objIpl != grpData.blockIPLName) do ( AppendIfUnique badIpls objIpl Append badIplObjs obj ) ) if (badIpls.count != 0) do ( local msg = "You're trying to add objects to a SLOD with a different IPL Group.\n\nWould you like to change the object IPLs?\n\n" Append msg ("SLOD IPL Group:\n" + grpData.blockIPLName) if (RsQueryBoxMultiBtn msg title:"Error: Adding mismatched IPLs" labels:#("Change IPL Groups", "Cancel") timeOut:-1) == 1 then ( for obj in badIplObjs do SetAttr obj idxIPLGroup grpData.blockIPLName ) else ( return OK ) ) ) pushPrompt ("Adding objects to group: " + (grpData.groupName as string)) -- Remove objects from all container-trees: for dataTag in dataTags where (dataTag.type == #container) do ( (dataTag.GetData()).removeObjs addObjsToGrps checkAll:True removeEmpty:False ) -- Add objects to specified slod-group local done = #{} grpData.addObjs addObjsToGrps done:&done if refreshView then ( RefreshTreeView() ) else ( -- (RefreshTreeView triggers updateVals, which does this automatically) for dataTag in dataTags where (dataTag.type == #container) do (dataTag.GetData()).RemoveEmptyGridBlocks() ) PopPrompt() return OK ) -- Add subgroup to selected node: fn ToolAddGroup objs:#() parentData: refreshView:true grpName: = ( if (parentData == unsupplied) do ( local selTag = (ToolGetSelTags())[1] parentData = selTag.GetData() ) -- Create new group(s) under parent-struct: local newGrps = parentData.CreateGroups objs grpName:grpName if refreshView do RefreshTreeView selItems:(for grp in newGrps where (grp.genLodLevel != 1) collect grp) return newGrps[1] ) on btnAddToGrp pressed do ( local thisGrp = (ToolGetSelGroupData())[1] ToolAddToGrp thisGrp ) on btnAddSubgroup pressed do ( local objs = if chkIncludeSel.checked then (GetCurrentSelection()) else #() ToolAddGroup objs:objs ) -- Delete selected groups: fn deleteGrps refreshView:true = ( for grpData in ToolGetSelGroupData() do ( grpData.deleteGroup() ) if refreshView do RefreshTreeView() ) on btnDelGrps pressed do ( deleteGrps() ) fn combineGrps = ( -- Can only run this command when items are siblings: local combineGrps = ToolGetSelGroupData() -- Collect groups' objects: local grpObjs = #() for item in combineGrps do ( join grpObjs (item.getObjs info:false) ) -- Delete selected groups: deleteGrps refreshView:false -- Add new group: ToolAddGroup parentData:(combineGrps[1].GetParentData()) objs:grpObjs ) on btnCombineGrps pressed do ( combineGrps() ) on rdoIslandDist changed state do ( spnIslandDist.enabled = (state == 2) ) -- Split selected group's object up into island-groups: fn splitToIslands = ( local selGrpTags = for item in dataTags where item.selected and ((item.GetData()).genLodLevel == 3) collect item if (selGrpTags.count == 0) do ( selGrpTags = for item in dataTags where item.selected and ((item.GetData()).genLodLevel == 2) collect item ) local success = true for item in selGrpTags while success do ( local grpData = item.GetData() pushPrompt ("Islanding group: " + grpData.groupName) success = gRsComboLodFuncs.splitGrpToIslands grpData useLodDist:(rdoIslandDist.state == 1) overrideDist:spnIslandDist.value popPrompt() ) RefreshTreeView() ) on btnSplitToIslands pressed do ( splitToIslands() ) -- Changes group blocking-params fn SetBlockingVal param val = ( local viewChanged = False local isPosX = (param == #posX) local isPosY = (param == #posY) local isPos = (isPosX or isPosY) -- If these parameters change, we won't update the tree immediately - -- 'SetRedrawRequired' will highlight the refresh-button instead local doTreeUpdate = True case of ( (param == #doResidents): -- Set selected maps as using Memory Resident models ( -- Get map root-node: local selMapData = ToolGetSelMapData() selMapData.doResidents = val doTreeUpdate = False viewChanged = True ) (param == #groupName): -- Rename group(s) ( -- Don't rename if string is empty, is only whitespace: local validName = ((trimLeft val) != "") -- Don't rename if string contains invalid characters: local invalidChars = " \t`¬!\"£$%^&*()=+{[}];:'@#~\\|,.<>/?" for n = 1 to invalidChars.count while validName do ( local char = invalidChars[n] validName = (findString val char == undefined) if not validName then ( messagebox ("Invalid character: \"" + char + "\"") title:"Rename Failure" ) ) if validName do ( local selGrpData = ToolGetSelGroupData() if (RsLodCombinerGroup.SetGrpNames selGrpData val) do UpdateAllNodeStatus() ) ) Default: ( local changeBlockType = (param == #ChildBlockType) or (param == #BlockByProp) doTreeUpdate = False viewChanged = True -- Get groups-nodes that are selected in the UI local selGrpData = ToolGetSelGroupData() -- Modify all selected group-nodes for item in selGrpData do ( local posWas = Copy item.childBlockPos case of ( (isPos and (item.childBlockPos == Undefined)):( ) isPosX: ( if (val == #Reset) then item.childBlockPos = undefined else item.childBlockPos.X = val ) isPosY:(item.childBlockPos.Y = val) default:(SetProperty item param val) ) -- ChildBlockPos is regenerated on ChildBlockType change: if changeBlockType do item.childBlockPos = Undefined -- Change values that will modify gridding if ((not isPos) and (changeBlockType)) or (isPos and (item.childBlockPos != undefined)) do ( -- Mark item as needing to be reblocked -- this will happen the next time RefreshTreeView is called item.reblockRequired = True -- Move area-outlines, if changed if (item.childBlockPos != undefined) and (posWas != item.childBlockPos) do ( local posDiff = (item.childBlockPos - posWas) for subGrp in (item.GetGroupList()) do ( subGrp.blockPos += posDiff subGrp.SetBlockGeom() ) ) ) ) ) ) if viewChanged do SetRedrawRequired True if isPos then ( if viewChanged do CompleteRedraw() ) else ( if viewChanged then ( if doTreeUpdate do RefreshTreeView() ) else ( -- Revert controls UpdateCtrls() ) ) return viewChanged ) on txtRename entered newText do ( setBlockingVal #groupName newText ) on lstBlockType selected num do ( setBlockingVal #childBlockType gRsLodCombinerVals.blockTypes[num] ) on spnBlockSize entered arg cancelled do (if not cancelled do (SetBlockingVal #ChildBlockRadius spnBlockSize.value)) on spnPosX changed val do (SetBlockingVal #PosX val midEdit:True) on spnPosY changed val do (SetBlockingVal #PosY val midEdit:True) on spnPosX entered arg cancelled do (if not cancelled do (SetBlockingVal #PosX spnPosX.value)) on spnPosY entered arg cancelled do (if not cancelled do (SetBlockingVal #PosY spnPosY.value)) on btnResetPos pressed do ( SetBlockingVal #PosX #Reset RefreshTreeView() ) on chkBlockByProp changed state do (SetBlockingVal #BlockByProp state) on chkDoResidents changed state do (setBlockingVal #DoResidents state) -- Viewport Preview checkboxes clicked: on chkShowBoxesInViewport changed state do (setViewportShow()) on chkShowShapesInViewport changed state do (setViewportShow()) on rdoShowSubtree changed state do (setViewportShow()) on chkShowNonLodding changed state do (RefreshTreeView()) -- LOD-generation buttons: fn doGenerate all:false doChildren:true = ( -- Update tree if we know that changes may have happened if isTreeRedrawRequired do RefreshTreeView() local selTags = ToolGetSelTags() local lodNodes = #() if all then ( lodNodes = for item in dataTags where (item.type == #container) collect item ) else ( lodNodes = for item in selTags where (isProperty (item.GetData()) #lodSynced) collect item -- If Container nodes are selected, add their children to the list: if doChildren do ( for item in selTags where (item.type == #container) do ( join lodNodes (for idx in item.childTagIdxs collect dataTags[idx]) ) ) ) local selData = for item in selTags collect item.GetData() selTags = undefined -- Recursively generate LODs: gRsComboLodFuncs.GenerateLods lodNodes:lodNodes force:chkForceGenerate.checked doChildren:doChildren lodNodes = undefined -- Refresh TreeView: RefreshTreeView selItems:selData doLodLinks:False gc() return OK ) on btnGenLodsAll pressed do ( doGenerate all:true ) on btnGenLodsNodeChilds pressed do ( doGenerate() ) on btnGenLodsNode pressed do ( doGenerate doChildren:false ) on btnOutputMeta pressed do ( local mapNodes = for item in dataTags where (item.type == #container) collect item local xmlAddQueue = #() local getVal = gRsComboLodFuncs.editResidentLists mapNodes xmlAddQueue:xmlAddQueue -- Abort if that step was cancelled/failed: if (getVal == False) do return False -- Save metafiles: for item in mapNodes do ( (item.GetData()).storeResidents() ) -- Add new metafiles to Perforce: gRsPerforce.postExportAdd exclusive:false silent:false queue:xmlAddQueue return True ) on btnAreaZoneGen pressed do ( filein (RsConfigGetWildwestDir() + "Script/3dsMax/Maps/MemResidentZones_UI.ms") ) fn setContainerLodParent newObj = ( local objCont = Containers.IsInContainer newObj local unsettingCont = (objCont == undefined) local selGrpData = ToolGetSelGroupData() -- We only want to add containerlods to top-level groups: local topGrps = makeUniqueArray (for item in selGrpData collect (if (isKindOf (item.GetParentData()) RsLodCombinerGroup) then (item.GetParentData()) else item)) selGrpData = undefined -- Set containerLod-parent value for groups with correct container: for grpData in topGrps where (unsettingCont) or ((grpData.GetParentData()).contNode == objCont) do ( grpData.containerLodParent = undefined if (newObj != undefined) do ( local ObjInfo = RsLodCombinerObject node:newObj lodLevel:-1 ObjInfo.SetParentData GrpData grpData.containerLodParent = ObjInfo ) ) -- Refreshing TreeView will update lod-links: RefreshTreeView() ) -- Give parent-groups new ContainerLod parent: on btnContLodParent picked newObj do ( setContainerLodParent newObj ) on btnContLodParent rightclick do ( setContainerLodParent undefined ) -- Recalculate lod-blocking for selected groups: on btnReblock pressed do ( local selGrps = ToolGetSelGroupData() for item in selGrps do item.ReassignBlocks() RefreshTreeView selItems:selGrps ) -- Remove selected objects from ComboLodder system: on btnRemSelFromGrps pressed do ( ToolRemoveObjs (selection as array) checkAll:True ) -- Generate billboard-meshes for ComboLodder's ContainerLod proxies: on btnGenContLodMeshes pressed do ( gRsComboLodFuncs.GenContLodMeshes dataTags sizeLimit:spnMinBBsize.value return OK ) -- Manually applies billboard-setup function to selected objects: on btnOptBBs pressed do ( local selObjs = (selection as array) gRsUlog.LogMessage "RsCopyBillboardDimensionsToShaders" context:"ComboLodder" RsCopyBillboardDimensionsToShaders selObjs optimiseMultiMtls:True ) -- Find selected objects in ComboLodder tree: on btnFindSelInTree pressed do ( -- Initially set all nodes as unselected: dataTags.selected = false -- Find selected objects in tool's object-list: local tagNums = #{} tagNums.count = dataTags.count for obj in selection do ( local findNum = findItem objsList obj if (findNum == 0) then ( if (RsIsContainerLod obj) do ( for item in containerLodList where (item.node == obj) do ( local tagIdx = (item.GetParentData()).tagIdx tagNums[tagIdx] = true ) ) ) else ( local objDataItem = objDataList[findNum] local tagIdx = objDataItem.tagIdx if (tagIdx == undefined) do ( -- Lod-objects have undefined tagIdx, so select their parent: tagIdx = (objDataItem.GetParentData()).tagIdx -- Select parent-parent-node for objects with hidden blocks: if (tagIdx == undefined) do ( tagIdx = ((objDataItem.GetParentData()).GetParentData()).tagIdx ) ) tagNums[tagIdx] = true ) ) local tagItems = for tagIdx in tagNums collect dataTags[tagIdx] if (tagItems.count != 0) do ( -- Set object-node tags as selected: tagItems.selected = true -- Sort items by tagIdx, to put them in order they are shown in treeview: fn sortByTagIdx v1 v2 = (v1.tagIdx - v2.tagIdx) qsort tagItems sortByTagIdx -- Set all parent-blocks as expanded: local itemParents = (makeUniqueArray (for item in tagItems collect ((item.GetData()).GetParentData()))) local parentNum = 0 while (parentNum < itemParents.count) do ( parentNum += 1 local thisNode = itemParents[parentNum] if (thisNode != undefined) and (thisNode.tagIdx != undefined) do ( thisNode.expanded = true dataTags[thisNode.tagIdx].treeNode.Expand() appendIfUnique itemParents (thisNode.GetParentData()) ) ) itemParents.count = 0 -- Ensure that selected nodes are visible: for n = 1 to tagItems.count do ( tagItems[n].treeNode.EnsureVisible() ) ) setViewportShow() treeViewCtrl.Invalidate() updateCtrls() return OK ) on btnRefresh pressed do ( RefreshTreeView doGC:false ) on btnDebug changed state do ( -- Turn Debug Mode on/off: RsComboLodDebug = state format "[ComboLodder Debug %]\n" (if state then "ON" else "OFF") -- Update node-labels: UpdateAllNodeStatus() ) ------------------------------------------------------------------------- -- .Net Control Event Handler Functions ------------------------------------------------------------------------- on treeViewCtrl drawNode Sender Arg do ( local drawNode = arg.node -- Don't draw if node-bounds haven't been defined yet: if (drawNode.bounds.width == 0) do return false local nodeTag = drawNode.tag if (nodeTag != undefined) do ( local nodeTag = nodeTag.value -- Set up node-details the first time it's drawn: if (nodeTag.smallText == Undefined) do UpdateNodeStatus nodeTag -- Draw text as aliased vector-shape: arg.Graphics.SmoothingMode = arg.Graphics.SmoothingMode.HighQuality -- Draw background-coloured rectangle to avoid overdrawing: Arg.Graphics.FillRectangle WindowBrush Arg.Bounds local drawShapes = #() local isContainer = false local isObject = false local isGroup = false local isBlock = false case nodeTag.type of ( #object:(isObject = true) #block:(isBlock = true) #group:(isGroup = true) #container:(isContainer = true) ) local isSelected = nodeTag.selected local nameText = nodeTag.nameText local smallText = (trimRight nodeTag.smallText) local hasSmallText = (smallText != "") local textStyle = nodeTag.textStyle local textSize = nodeTag.textSize local textBrush = nodeTag.textBrush local left = Arg.Bounds.X local top = Arg.Bounds.Y local height = Arg.Bounds.Height local nodeLeft = drawNode.Bounds.X local nodeTop = drawNode.Bounds.Y local nodeHeight = drawNode.Bounds.Height local nodeWidth = drawNode.Bounds.Width local nodeMidLeft = [nodeLeft, nodeTop + (nodeHeight / 2)] local checkBoxSpace = 4 local checkBoxSize = 10 local halfCheckBoxSize = (0.5 * checkBoxSize) local checkBoxCentre = nodeMidLeft - [halfCheckBoxSize + checkBoxSpace, 0] local showCheckbox = (drawNode.Nodes.Count != 0) local nodeIndex = drawNode.Index local isTopNode = (drawNode.Level == 0) -- Draw tree-bits: ( local lineStartPos = copy checkBoxCentre if not isTopNode do ( lineStartPos.x -= 19 ) local leftDist = nodeMidLeft.X - lineStartPos.X if not isTopNode do ( local lineEndPos = copy nodeMidLeft if showCheckbox do ( lineEndPos.X -= halfCheckBoxSize ) -- Draw horizontal line: Arg.Graphics.DrawLine DottedPen lineStartPos.X lineStartPos.Y lineEndPos.X lineEndPos.Y -- Draw vertical sibling-lines: local thisNode = drawNode local heightDiff = 0.5 * Sender.ItemHeight local yDist = (top + height) while (thisNode != undefined) do ( local xDist = thisNode.Bounds.X - leftDist if (thisNode.NextNode != undefined) do ( Arg.Graphics.DrawLine DottedPen xDist top xDist yDist ) thisNode = thisNode.Parent ) ) -- Drawn line up to parent: if (not isTopNode) and (drawNode.NextNode == undefined) do ( local xDist = nodeLeft - leftDist Arg.Graphics.DrawLine DottedPen xDist top xDist checkBoxCentre.Y ) local isExpanded = drawNode.IsExpanded -- Draw line down from checkbox to next node: local vertLineX = checkBoxCentre.X if isExpanded and (drawNode.Nodes.Count != 0) do ( local lineStartY = checkBoxCentre.Y local lineEndY = Arg.Bounds.Bottom if showCheckbox do ( lineStartY += halfCheckBoxSize ) Arg.Graphics.DrawLine DottedPen vertLineX lineStartY vertLineX lineEndY ) -- Draw expander-box: if showCheckbox do ( local ExpanderShape = dotNetObject "System.Drawing.Drawing2D.GraphicsPath" local boxPos = checkBoxCentre - halfCheckBoxSize local boxRect = dotNetObject "System.Drawing.Rectangle" boxPos.x boxPos.y (checkBoxSize as integer) (checkBoxSize as integer) Arg.Graphics.FillRectangle DefaultBrush boxRect Arg.Graphics.DrawRectangle ThinPen boxRect local textPos = dotNetObject "System.Drawing.Point" boxRect.X boxRect.Y local ExpanderText if isExpanded then ( ExpanderText = "-"; textPos.X += 1; textPos.Y -= 1 ) else ( ExpanderText = "+"; textPos.X -= 1; textPos.Y -= 2 ) ExpanderShape.AddString ExpanderText fontFamily 1 10 textPos StringFormat Arg.Graphics.FillPath BlackBrush ExpanderShape ) ) local textPos = dotNetObject "System.Drawing.Point" nodeLeft nodeMidLeft.Y -- Draw status-colour symbol: if (nodeTag.statusBrush != undefined) do ( local statusShape = dotNetObject "System.Drawing.Drawing2D.GraphicsPath" append drawShapes (dataPair shape:statusShape brush:nodeTag.statusBrush) local circleSize = 20 statusShape.AddString "l" SymbolFontFamily 0 circleSize textPos StringFormat local textXformer = dotNetObject "System.Drawing.Drawing2D.Matrix" textXformer.Translate -1 -11 statusShape.Transform textXformer -- Draw sub-node status-colour circle: if (nodeTag.subStatusBrush != undefined) do ( local subStatusShape = dotNetObject "System.Drawing.Drawing2D.GraphicsPath" append drawShapes (dataPair shape:subStatusShape brush:nodeTag.subStatusBrush) local subCircleSize = 9 subStatusShape.AddString "l" SymbolFontFamily 0 subCircleSize textPos StringFormat local textXformer = dotNetObject "System.Drawing.Drawing2D.Matrix" textXformer.Translate 10 0 subStatusShape.Transform textXformer ) textPos.X += circleSize ) local textShape = dotNetObject "System.Drawing.Drawing2D.GraphicsPath" append drawShapes (dataPair shape:textShape brush:textBrush) textShape.AddString nameText fontFamily textStyle textSize textPos StringFormat local textBounds = textShape.GetBounds() -- Move name-shape to middle of node-height: local textXformer = dotNetObject "System.Drawing.Drawing2D.Matrix" textXformer.Translate 0 -((0.5 * textBounds.Height) + 3) textShape.Transform textXformer if hasSmallText do ( -- Add extra text as another shape: local smallTextShape = dotNetObject "System.Drawing.Drawing2D.GraphicsPath" append drawShapes (dataPair shape:smallTextShape brush:DefaultBrush) textPos.x = textBounds.Right + 13 smallTextShape.AddString smallText FontFamily 1 9 textPos StringFormat -- Centre the extra text: textBounds = smallTextShape.GetBounds() textXformer = dotNetObject "System.Drawing.Drawing2D.Matrix" textXformer.Translate 0 -(0.5 * textBounds.Height) smallTextShape.Transform textXformer textBounds = smallTextShape.GetBounds() -- Draw box around info-text: local boxPadding = 4 local outlineRect = dotNetObject "System.Drawing.Rectangle" (textBounds.X - boxPadding + 1) (textBounds.Y - boxPadding) (textBounds.width + 2 * boxPadding) (textBounds.height + 2 * boxPadding) Arg.Graphics.DrawRectangle DottedPen outlineRect ) -- Draw selection-box: if isSelected do ( local padSize = outerPen.Width + 3 textBounds = textShape.GetBounds() local selectRect = dotNetObject "System.Drawing.Rectangle" (textBounds.X - padSize + 1) (textBounds.Y - padSize + 1) (textBounds.Width + 2 * padSize) (textBounds.Height + 2 * padSize) local selBrush = SelectBrushes.normal arg.graphics.fillRectangle selBrush selectRect ) -- Draw text-outlines and fill: for item in drawShapes do ( arg.graphics.DrawPath outerPen item.shape if (item.brush != undefined) do ( arg.graphics.FillPath item.brush item.shape ) ) ) ) on treeViewCtrl afterSelect sender arg do ( ) local cancelExpandCollapse = false on treeViewCtrl MouseDown Sender Arg do ( local clickItem = sender.hitTest Arg.x Arg.y local clickNode = clickItem.node cancelExpandCollapse = (Arg.Clicks > 1) if (clickNode == undefined) do return False local nodeTag = clickNode.tag.value if (nodeTag.type == #none) do return False local rightClick = arg.button.equals arg.button.right local ctrlPressed = ((dotNetClass "control").modifierKeys.equals (dotNetClass "keys").control) local shiftPressed = ((dotNetClass "control").modifierKeys.equals (dotNetClass "keys").shift) case of ( shiftPressed: ( -- Select every tag with same type between here and there: if (lastSelNodeTag != undefined) and (lastSelNodeTag.Type == nodeTag.Type) then ( -- Get tag-numbers for start/end of selection, and put them in the right order... local tagIdxs = #(lastSelNodeTag.tagIdx, nodeTag.tagIdx) sort tagIdxs -- Set all matching-type tags in-between as selected: for n = tagIdxs[1] to tagIdxs[2] do ( local thisTag = dataTags[n] if (thisTag.type == nodeTag.Type) do ( thisTag.selected = true ) ) ) else ( nodeTag.selected = true ) ) ctrlPressed: ( if nodeTag.selected then ( if not rightClick do ( nodeTag.selected = false ) ) else ( -- Only allow multi-select of same-type nodes: local selectedType for item in dataTags where item.selected while (selectedType == undefined) do ( selectedType = item.type ) if (selectedType == undefined) or (selectedType == nodeTag.type) do ( nodeTag.selected = true ) ) ) default: ( if (not rightClick) or (not nodeTag.selected) do ( dataTags.selected = false nodeTag.selected = true ) ) ) if nodeTag.selected and ((not shiftPressed) or (lastSelNodeTag == undefined)) do ( lastSelNodeTag = nodeTag ) -- Redraw Viewport, with new selection: setViewportShow() -- Redraw tree: Sender.invalidate() -- Update block-param controls: updateCtrls() -- Show rightclick menu: if rightClick and nodeTag.selected do ( popUpMenu ::RSmenu_LodCombiner ) OK ) -- TreeView doubleclick: on treeViewCtrl DoubleClick Sender Arg do ( local clickItem = sender.hitTest arg.x arg.y local clickNode = clickItem.node if (clickNode == undefined) do return false local nodeTag = clickNode.tag.value if (nodeTag.GetData() == undefined) do return false local selObjs = (nodeTag.GetData()).getObjs() clearSelection() select selObjs completeRedraw() ) -- Cancel collapse/expand if triggerd by doubleclick: on treeViewCtrl BeforeExpand Sender Arg do ( if cancelExpandCollapse then ( Arg.Cancel = true ) else ( -- Store expanded-state to data-struct: if (Arg.Node.Tag != undefined) do ( local structData = Arg.Node.Tag.Value.GetData() structData.Expanded = true completeRedraw() ) ) ) on treeViewCtrl BeforeCollapse Sender Arg do ( if cancelExpandCollapse then ( Arg.Cancel = true ) else ( -- Store expanded-state to data-struct: if (Arg.Node.Tag != undefined) do ( local structData = Arg.Node.Tag.Value.GetData() structData.Expanded = false completeRedraw() ) ) ) -- Make sure that treeview updates once control is restored after file-load (if it hasn't done so already after a viewport-redraw:) on treeViewCtrl MouseMove Sender Arg do ( if waitForLoad do viewportDraw() ) -- Save all data-changes to data-node paramblocks: fn store = ( if allowStore do ( for item in mapData do ( item.store() ) ) ) local oldSize on RsLodCombinerTool resized newSize do ( if (newSize.X < minRolloutSize.X) do ( RsLodCombinerTool.width = newSize.X = minRolloutSize.X ) if (newSize.Y < minRolloutSize.Y) do ( RsLodCombinerTool.height = newSize.Y = minRolloutSize.Y ) local ctrls = RsLodCombinerTool.controls local sizeDiff = newSize - oldSize oldSize = newSize -- Resize tree-control: treeViewCtrl.width += sizeDiff.x treeViewCtrl.height += sizeDiff.y -- Move the rest of the controls left/right: for n = 2 to ctrls.count do ( ctrls[n].pos.x += sizeDiff.x ) ) -- Clear data-arrays in anticipation of file-load: -- Rollout/data updates will be deferred until the next redraw after the RsRef system has activated: fn startWaitForLoad merging:false = ( if not merging do ( mapData.count = 0 dataTags.count = 0 ) waitForLoad = true ) fn nodeEventStartFunc ev nums = ( nodeCallback_needsTreeRedraw = False nodeCallback_doViewRedraw = False ) fn nodeEventEndFunc ev nums = ( if nodeCallback_needsTreeRedraw then ( --RefreshTreeView doGC:false ) else ( -- Do viewport-redraw if any bounds have changed: if nodeCallback_doViewRedraw do CompleteRedraw() ) nodeCallback_needsTreeRedraw = False nodeCallback_doViewRedraw = False ) -- Let tool know when its objects have been edited: fn nodeEventFunc ev nums objs: = ( -- Don't do anything if callbacks are deactivated, or map is being exported: if (not (RsLodCombinerTool.open and callbacksActive)) or (isMapExportActive()) do return false local changedLodObjs = #() local changedPropObjs = #() if (objs == unsupplied) do ( objs = for nodeHandle in nums collect (getAnimByHandle nodeHandle) ) -- Get ComboLodder data-structs for edited objects: for obj in objs do ( local objNum = findItem RsLodCombinerTool.objsList obj if (objNum != 0) do ( local objData = objDataList[objNum] if (objData.lodLevel != 0) then ( append changedLodObjs objData ) else ( append changedPropObjs objData ) ) ) local changedLods = (changedLodObjs.count != 0) local changedProps = (changedPropObjs.count != 0) -- Abort if no comboLodder objects were changed... if not (changedLods or changedProps) do return false local topChangedGridIdxs = #{} local updateBoundsGrps = #{} -- #linkChanged response is mostly the same as #deleted local linkChanged = (ev == #linkChanged) if linkChanged do ( ev = #deleted ) case ev of ( #hideChanged: -- Object-visibility was changed: ( /* setViewportHidden() completeRedraw() */ ) #deleted: -- Objects were deleted: ( for objData in changedLodObjs do ( local doRemove = True local lodObj = objData.node local isValidLodObj = (isValidNode lodObj) -- Don't remove from container's tree if object is still in container: if linkChanged and isValidLodObj do ( local objRoot = (objData.GetParentData()).getRootData() local objCont = (Containers.IsInContainer lodObj) if (objCont == objRoot.contNode) do ( doRemove = False ) ) if doRemove do ( -- Delete removed lods: if isValidLodObj do ( undo off (delete lodObj) ) local objParentData = objData.GetParentData() objParentData.lodObjItem = Undefined objParentData.UpdateVals() nodeCallback_needsTreeRedraw = True ) ) for objData in changedPropObjs do ( local doRemove = True -- Don't remove from container's tree if object is still in container: if linkChanged and (isValidNode objData.node) do ( local objRoot = (objData.GetParentData()).getRootData() local objCont = (Containers.IsInContainer objData.node) if (objCont == objRoot.contNode) do ( doRemove = False ) ) if doRemove do ( -- Mark gridding-parent as needing a clear-out: if (objData.gridParentIdxList.count != 0) do ( local gridParentIdx = objData.gridParentIdxList[1] topChangedGridIdxs[gridParentIdx] = True ) -- Remove object-item from parent's objItems list: local parentData = objData.GetParentData() local parObjItems = parentData.GetObjItems() local objNum = findItem parObjItems objData if (objNum != 0) do DeleteItem parObjItems objNum nodeCallback_needsTreeRedraw = True ) ) ) Default: -- Non-deletion/visiblity object-changes: ( -- Load slod-data for new refdefs, if objects have new ones: local changedRefObjItems = for objData in changedPropObjs where (isProperty objData.node #refDef) collect objData local changedObjRefs = (for objData in changedRefObjItems collect objData.node.refDef) gRsComboLodFuncs.loadSlodData changedObjRefs -- Request a tree-redraw if any objects now have different ref-data: for objData in changedRefObjItems while not nodeCallback_needsTreeRedraw do ( local objRef = objData.node.refDef local objRefHash = if (objRef == undefined) or (objRef.comboLod == undefined) then 0 else (objRef.comboLod.dataHash) nodeCallback_needsTreeRedraw = (objData.refHash != objRefHash) ) -- Any changes to lod-objects will cause them to be invalidated automatically: changedLodObjs = for objData in changedLodObjs where (objData.sourceHash != 0) collect objData if (changedLodObjs.count != 0) do ( changedLodObjs.sourceHash = 0 nodeCallback_needsTreeRedraw = True ) local isControllerEvent = (ev == #controllerOtherEvent) local addToGrpObjs = #() local remFromGrpObjIdxs = #() local griddedPropObjs = for objData in changedPropObjs where (objData.gridParentIdxList.count != 0) collect objData for objData in griddedPropObjs do ( -- Set redraw-flag if object-hash has changed: local hashWas = objData.sourceHash objData.UpdateVals() local objChanged = (objData.sourceHash != hashWas) if not nodeCallback_needsTreeRedraw do ( -- Do tree-redraw if group-status will change: if objChanged and ((objData.GetParentData()).lodSynced != false) then ( nodeCallback_needsTreeRedraw = True ) else ( -- Try updating object's status-text, to see if it changes: local objDataTag = dataTags[objData.tagIdx] local statusHashWas = objDataTag.statusHash if (statusHashWas != 0) do ( -- Request tree-redraw if status-string is different now: UpdateNodeStatus objDataTag nodeCallback_needsTreeRedraw = (statusHashWas != objDataTag.statusHash) ) ) ) -- See if prop has been moved out of its grid-blocks... if objChanged and isControllerEvent and (objData.gridParentIdxList.count != 0) do ( local obj = objData.node local sameBlock = true updateBoundsGrps += (objData.gridParentIdxList as bitArray) -- Has object been moved out of any of it's gridding-areas? for grpIdx in objData.gridParentIdxList while sameBlock do ( sameBlock = (dataTags[grpIdx].GetData()).isInBlockArea obj -- If object doesn't fit into this group... if not sameBlock do ( -- Mark object for adding to parent of this block: local addToGrpIdx = ((dataTags[grpIdx].GetData()).GetParentData()).tagIdx if (addToGrpObjs[addToGrpIdx] == undefined) do ( addToGrpObjs[addToGrpIdx] = #() ) append addToGrpObjs[addToGrpIdx] obj -- Mark object for removing from its current block: local objGrpIdx = (objData.GetParentData()).tagIdx if (objGrpIdx != undefined) do ( if (remFromGrpObjIdxs[objGrpIdx] == undefined) do ( remFromGrpObjIdxs[objGrpIdx] = #{} ) remFromGrpObjIdxs[objGrpIdx][objData.tagIdx] = True -- Mark topmost gridding-parent as needing an empty-blocks check: local topIdx = objData.gridParentIdxList[1] topChangedGridIdxs[topIdx] = True ) ) ) ) ) griddedPropObjs = undefined -- Update edited-group boundaries: local updateGrps = for grpIdx in updateBoundsGrps collect (dataTags[grpIdx].GetData()) updateGrps.boundsSize = undefined for grp in updateGrps do ( grp.UpdateBounds() ) updateGrps = undefined -- Remove any moved objects from their current groups: for grpIdx = 1 to remFromGrpObjIdxs.count where (remFromGrpObjIdxs[grpIdx] != undefined) do ( local grpData = dataTags[grpIdx].GetData() local remObjIdxs = remFromGrpObjIdxs[grpIdx] -- Filter out marked objects: local newObjItems = for objItem in grpData.GetObjItems() where (objItem.tagIdx != Undefined) and (not remObjIdxs[objItem.tagIdx]) collect objItem grpData.SetObjItems newObjItems -- Trigger tree-refresh: nodeCallback_needsTreeRedraw = True ) -- Add any moved objects to their new homes: for grpIdx = 1 to addToGrpObjs.count where (addToGrpObjs[grpIdx] != undefined) do ( -- Filter out marked objects: local done = #{} (dataTags[grpIdx].GetData()).AddObjs addToGrpObjs[grpIdx] done:&done doRedraw:False ) ) ) local changedLodObjs = #() local changedPropObjs = #() -- Check for groups that need to be removed: for idx in topChangedGridIdxs do ( (dataTags[idx].GetData()).RemoveEmptyGridBlocks() ) -- Show red outline around Redraw Tree button if nodeCallback_needsTreeRedraw do RsLodCombinerTool.SetRedrawRequired True -- Do viewport-redraw if any bounds have changed: if (updateBoundsGrps.numberSet != 0) do nodeCallback_doViewRedraw = True return OK ) fn abortToolOpen silent:False = ( if not silent do ( messageBox "Closing ComboLodder tool" title:"ComboLodder failed to open" ) allowStore = False destroyDialog RsLodCombinerTool allowStore = RsComboLodDebug_AllowStore ) fn postOpen = ( callbacksActive = False if (RsRefFuncs.databaseActive toolName:"ComboLodder") then ( callbacksActive = True RsLodCombinerTool.RefreshTreeView() ) else ( abortToolOpen() ) ) on RsLodCombinerTool open do ( if not allowStore do ( format "\nDEBUG: !!!COMBOLODDER DATA-STORE IS DISABLED!!!\n\n" ) RsLodCombinerTool.title += gRsLodCombinerVals.versionString -- Abort setup if data-load was cancelled: if not (Init()) do ( abortToolOpen() return False ) RefreshTreeView doGC:false oldSize = [RsLodCombinerTool.width, RsLodCombinerTool.height] callbacks.addScript #filePreSaveProcess "RsLodCombinerTool.Store()" id:#RsLodCombinerTool callbacks.addScript #objectXrefPreMerge "RsLodCombinerTool.StartWaitForLoad merging:True" id:#RsLodCombinerTool callbacks.addScript #objectXrefPostMerge "RsLodCombinerTool.postOpen()" id:#RsLodCombinerTool callbacks.addScript #filePreMerge "RsLodCombinerTool.StartWaitForLoad merging:True" id:#RsLodCombinerTool callbacks.addScript #filePostMerge "RsLodCombinerTool.postOpen()" id:#RsLodCombinerTool -- Close tool without storing data if closing scene: callbacks.addScript #filePreOpenProcess "RsLodCombinerTool.abortToolOpen silent:True" id:#RsLodCombinerTool callbacks.addScript #preSystemShutdown "RsLodCombinerTool.abortToolOpen silent:True" id:#RsLodCombinerTool callbacks.addScript #systemPreReset "RsLodCombinerTool.abortToolOpen silent:True" id:#RsLodCombinerTool callbacks.addScript #systemPreNew "RsLodCombinerTool.abortToolOpen silent:True" id:#RsLodCombinerTool registerRedrawViewsCallback viewportDraw nodeCallback = NodeEventCallback mouseUp:true delay:1000 \ materialStructured:nodeEventFunc geometryChanged:nodeEventFunc \ --hideChanged:nodeEventFunc \ deleted:nodeEventFunc topologyChanged:nodeEventFunc controllerOtherEvent:nodeEventFunc linkChanged:nodeEventFunc \ callbackBegin:nodeEventStartFunc callbackEnd:nodeEventEndFunc ) -- Remove callbacks and dispose of unnecessary data: on RsLodCombinerTool close do ( callbacksActive = false unregisterRedrawViewsCallback viewportDraw callbacks.removeScripts id:#RsLodCombinerTool nodeCallback = undefined -- Store changes to helper-objects: Store() -- Kill off rollout's data: objsList.count = 0 objDataList.count = 0 containerLodList.count = 0 mapData.count = 0 clearTreeData() treeViewCtrl.dispose() gc light:true (dotnetclass "system.gc").collect() completeRedraw() ) -- Create tool-rollout: fn create = ( try (destroyDialog ::RsLodCombinerTool) catch () createDialog RsLodCombinerTool style:#(#style_titlebar, #style_resizing, #style_sysmenu, #style_maximizebox) return OK ) ) -- Rightclick-menu for ComboLod tool's treeview control: rcmenu RSmenu_LodCombiner ( local isValidSel local selType local lodObjsAll, lodObjsNode fn isObjects = (RsLodCombinerTool.isObject) fn isntObjects = (not RsLodCombinerTool.isObject) fn isGroups = (RsLodCombinerTool.isGroup) fn isContainers = (RsLodCombinerTool.isContainer) fn isntContainer = (not RsLodCombinerTool.isContainer) fn isBlock = (RsLodCombinerTool.isBlock) fn isntBlock = (not RsLodCombinerTool.isBlock) fn singleNodeSel = (RsLodCombinerTool.isSingleSel) fn multiGroups = (not (singleNodeSel()) and isGroups()) fn singleGroup = (singleNodeSel() and isGroups()) fn canAddToGroup = (RsLodCombinerTool.btnAddToGrp.enabled and (selection.count != 0)) fn isSelectable = (isValidSel) fn nonObjSelectable = (isValidSel and not RsLodCombinerTool.isObject) fn hasLodObjs = (lodObjsAll.count != 0) fn nodeHasLodObjs = (lodObjsNode.count != 0) fn anySelectable = (isSelectable() or hasLodObjs()) fn canAddSubGrps = (RsLodCombinerTool.canAddGrp) fn canCombineGrps = (RsLodCombinerTool.canCombine) fn canSplitToIslands = (RsLodCombinerTool.btnSplitToIslands.enabled) fn canGenNode = (RsLodCombinerTool.btnGenLodsNode.enabled) fn canGenNodeChilds = (RsLodCombinerTool.btnGenLodsNodeChilds.enabled) fn anyCanGen = (canGenNode() or canGenNodeChilds()) fn debugMode = (RsComboLodDebug) fn debugSingleSel = (RsComboLodDebug and (singleNodeSel())) fn debugSingleSelHasLodObjs = ((debugSingleSel()) and (nodeHasLodObjs())) fn canPrintGuid = ((debugSingleSel()) and (isGroups() or isBlock())) fn GetSelNodeTags = ( for item in RsLodCombinerTool.dataTags where item.selected collect item ) ------------------------------------- -- MENU-ITEMS: ------------------------------------- menuItem itmAddGrp "Add subgroup" filter:canAddSubGrps menuItem itmDeleteGrps "Del grps" filter:isGroups menuItem itmCombineGrps "Combine groups" filter:canCombineGrps menuItem itmSplitToIslands "Split group to islands" filter:canSplitToIslands separator sepA menuItem itmAddObjsToGrp "Add selected props to SLOD" filter:canAddToGroup menuItem itemRemObjs "xxx" filter:isObjects separator sepB filter:anyCanGen menuItem itmGenLodsNode "Generate LODs for node" filter:canGenNode menuItem itmGenLodsNodeChilds "Generate LODs for node and children" filter:canGenNodeChilds separator sepD filter:anySelectable menuItem itmSelProp "Select prop" filter:isObjects subMenu "Select props" filter:nonObjSelectable ( menuItem itmSelProps "Select props" separator sepE menuItem itmSelLodProps "Select lod-source props" menuItem itmSelNonLodProps "Select non-lodding props" menuItem itmSelResidentProps "Select memory-resident props" ) subMenu "Select generated LODs" filter:hasLodObjs ( menuItem itmSelLodObjsAll "For node and children" filter:isntBlock menuItem itmSelLodObjsNode "For node" filter:nodeHasLodObjs ) separator sepF filter:debugMode menuItem itmNodeDebug "Print Node Data" filter:debugSingleSel menuItem itmNodeLodDebug "Print Node's LOD-obj Data" filter:debugSingleSelHasLodObjs menuItem itmPrintGUID "Print Node's GUID" filter:canPrintGuid -- Set up rightclick-menu values & items: on RSmenu_LodCombiner open do ( local selNodeTags = GetSelNodeTags() isValidSel = (selNodeTags[1] != undefined) selType = if (not isValidSel) then #none else selNodeTags[1].type -- See if more than one node-type was selected somehow: local singleType = true for n = 2 to selNodeTags.count while singleType do ( local thisTag = selNodeTags[n] if (thisTag.type != selType) do ( selType = #multi singleType = false ) ) lodObjsAll = #() lodObjsNode = #() if isValidSel do ( for item in selNodeTags do ( local tagData = item.GetData() -- Get lod-objects for selected node-hierarchies: if (isProperty tagData #getLodObjs) do ( join lodObjsAll (tagData.getLodObjs info:false recurse:true) join lodObjsNode (tagData.getLodObjs info:false recurse:false) ) ) ) local isPlural = (selNodeTags.count != 1) local plural = if isPlural then "s" else "" itmDeleteGrps.text = ("Delete Group" + plural) local objPlural = if ((selType == #object) and (selNodeTags.count == 1)) then "" else "s" local nodeText = case of ( (selType == #object):"" (isPlural):("nodes' ") default:("node's ") ) itmSelProp.text = "Select " + nodeText + "prop" + objPlural itemRemObjs.text = "Remove prop" + objPlural selNodeTags.count = 0 ) on itmGenLodsNode picked do ( RsLodCombinerTool.doGenerate doChildren:false ) on itmGenLodsNodeChilds picked do ( RsLodCombinerTool.doGenerate doChildren:true ) on itmDeleteGrps picked do ( RsLodCombinerTool.deleteGrps() ) on itmCombineGrps picked do ( RsLodCombinerTool.combineGrps() ) on itmSplitToIslands picked do ( RsLodCombinerTool.splitToIslands() ) -- Select node-objects: fn selObjs justLodder:False justNonLodder:False justResident:False = ( local selObjItems = #() for item in (GetSelNodeTags()) do ( join selObjItems ((item.GetData()).getObjs info:True) ) -- Filter out lodder/non-lodder props: case of ( (justLodder or justNonLodder): ( selObjItems = for item in selObjItems where (item.canLod == justLodder) collect item ) (justResident): ( selObjItems = for item in selObjItems where (item.isResidentProp) collect item ) ) local selObjs = for item in selObjItems collect item.node selObjItems.count = 0 clearSelection() select selObjs ) on itmSelProp picked do (selObjs()) on itmSelProps picked do (selObjs()) on itmSelLodProps picked do (selObjs justLodder:True) on itmSelNonLodProps picked do (selObjs justNonLodder:True) on itmSelResidentProps picked do (selObjs justResident:True) -- Select node's generated LOD objects: on itmSelLodObjsAll picked do ( select lodObjsAll ) on itmSelLodObjsNode picked do ( select lodObjsNode ) -- Add selected objects to selected group, if they're in the right container: on itmAddObjsToGrp picked do ( RsLodCombinerTool.ToolAddToGrp ((GetSelNodeTags())[1].GetData()) ) -- Add new subgroup to selected node: on itmAddGrp picked do ( RsLodCombinerTool.ToolAddGroup() ) -- Remove selected objects from groups: on itemRemObjs picked do ( local selObjs = for item in (GetSelNodeTags()) collect (item.GetData()).node RsLodCombinerTool.ToolRemoveObjs selObjs checkAll:True ) -- Print node-data to Listener: on itmNodeDebug picked do ( local nodeData = (GetSelNodeTags())[1].GetData() format "\n" RsPrintStruct nodeData format "\n" ) on itmNodeLodDebug picked do ( format "\n" RsPrintStruct ((GetSelNodeTags())[1].GetData()).lodObjItem format "\n" ) on itmPrintGUID picked do ( format "\n%\n\n" ((GetSelNodeTags())[1].GetData()).GUID ) ) RsLodCombinerTool.create() GlobalVars.Remove #RsComboLodDebug_AllowStore GlobalVars.Remove #RsComboLodDebug_EnableBlockPerObjectName -- TEST-FUNCTIONS IF FALSE DO ( -- Print info about the last-clicked tree-node: RsPrintStruct (RsLodCombinerTool.lastSelNodeTag.GetData()) )