2980 lines
91 KiB
Plaintext
Executable File
2980 lines
91 KiB
Plaintext
Executable File
-----------------------------------------------------------------------------
|
|
-- 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())
|
|
) |