4702 lines
136 KiB
Plaintext
Executable File
4702 lines
136 KiB
Plaintext
Executable File
-----------------------------------------------------------------------------
|
|
-- Combined-LOD Generator Tool (ComboLodder)
|
|
-----------------------------------------------------------------------------
|
|
-- Data-structures (loaded by lodCombinerTool_UI.ms)
|
|
-----------------------------------------------------------------------------
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Uses
|
|
-----------------------------------------------------------------------------
|
|
-- Print verbose debug-data to listener:
|
|
global RsComboLodDebug = False
|
|
global gRsLodCombinerVals
|
|
|
|
RSTA_LoadCommonFunction #("FN_Geometry.ms")
|
|
filein (RsConfigGetWildWestDir() + "script/3dsMax/_Common_Functions/FN_Objects.ms")
|
|
filein (RsConfigGetWildwestDir() + "Script/3dsMax/Maps/MemResidentZones.ms")
|
|
filein (RsConfigGetWildwestDir() + "Script/3dsMax/Maps/lodCombinerTool_funcs.ms")
|
|
|
|
-- Struct used to store various values used by this script:
|
|
struct RsLodCombinerVals
|
|
(
|
|
-- Changing the integer part of VersionNum will invalidate existing LOD-objects, and will warn people if they're opening data from newer version of tool.
|
|
-- Changing the floating-point bit has no effect, so can be used to track non-data tool-changes.
|
|
versionNum = 11.24,
|
|
versionName = "Magma Carp",
|
|
|
|
dataVersion, -- 'VersionNum' as integer
|
|
dummySlodVersion = 2, -- Used to update dummySlod meshes
|
|
versionString,
|
|
|
|
lodSuffixes = #("LOD", "SLOD", "SLOD2"),
|
|
dummySlodSuffix = "dSLOD",
|
|
lodDistUnits = #(400, 750, 1500), -- Values to be used for LOD-object distances
|
|
defBlockRadius = #(-1, 50, 100), -- Default block-radius for sub-groups
|
|
|
|
maxIplCount = 1000, -- We don't allow IPL Groups to have more objects than this in the scene
|
|
|
|
billboardMatVersion = 4 + (100 * RsComboLod_billboardMatVersion), -- Billboard-objects are invalidated if this changes
|
|
|
|
natAOMult = 254, -- "AO Multiplier" value used for billboard-lods
|
|
artAOMult = 0, -- "Artificial AO Multiplier" value used for billboard-lods
|
|
|
|
textureScale = 0.25, -- ComboLod materials are given this texture-scale in Max
|
|
|
|
meshTintVersion = 1, -- Meshtint-generation method's version
|
|
propLodTexDir = (RsConfigGetTextureSourceDir core:True + "Props/Prop_Lods/lod_textures/"),
|
|
lodTintPattern = "*prop_lod_TINTED*", -- Filename-pattern used to indicate that faces are to be tinted
|
|
lodColourFile = (propLodTexDir + "prop_lod.bmp"), -- Filename to apply prop-lod colour-texture:
|
|
defaultTint = (color 199 194 188), -- Default untinted colour for meshtinted objects
|
|
|
|
defaultMat, -- Default material for replacing undefined/temp mats. Uses lodColourFile as texture.
|
|
|
|
-- Path for storing Memory Resident model-lists:
|
|
residentPath = (RsMakeSafePath (RsConfigGetCommonDir() + "/data/levels/" + RsConfigGetProjectName() + "/resident/")),
|
|
-- Memory Resident model-lists generated elsewhere, that will be read by this tool:
|
|
residentGridPath = (residentPath + "grid/"),
|
|
|
|
blockTypes = #(#box, #hex, #tri),
|
|
blockTypeNames = #("Square", "Hexagon", "Triangle"),
|
|
|
|
maxLodDist = 350, -- Max LOD-distance to be set on first-level LOD-objects
|
|
maxLodCount = 255, -- Max number of lod-children per lod-object
|
|
maxLodWidth = 400, -- Max width/length of lod-block
|
|
maxLodRadius, -- Set to (maxLodWidth / 2)
|
|
|
|
minFaceSize = 3.0, -- Don't use objects whose biggest billboard-face is smaller than this
|
|
tiltAltAngle = 5.0, -- Use Tiltable Alternative mesh for a prop if it is tilted more than this
|
|
minBoxSizes = #(1.5, 2.4, 3), -- Minimum bounding-box edge-lengths for each LOD-level. Don't include an instance that is smaller than this.
|
|
|
|
-- Memory Resident cut-off values: --
|
|
resGridCountRatio = 0.5, -- Make a model Resident if its props are used in this fraction of all unique 50x50m grids used by loddable props.
|
|
resPropCountRatio = 0.05, -- Make a model Resident if its loddable props make up this fraction of the map's loddable props.
|
|
-----------------------------------------------
|
|
resTinyBlockCount = 5, -- Make a model Resident if it is used in more than this many Tiny Blocks (set to -1 to deactivate Tiny-Block checking)
|
|
resTinyBlockSize = 2, -- A lod-block is considered to be a Tiny Block if it lods this many props, or fewer:
|
|
-----------------------------------------------
|
|
resMaxDistRange = 60.0, -- Don't allow Resident lod-blocks with props with lod-distance range greater than this.
|
|
|
|
-- Use these values instead to deactivate Memory Resident bits:
|
|
--resGridCountRatio = -1,
|
|
--resPropCountRatio = -1,
|
|
--resTinyBlockCount = -1,
|
|
|
|
|
|
-- List of properties to be copied when cloning a RsLodCombinerGroup
|
|
grpClonePropNames = Undefined,
|
|
|
|
-----------------------------------------------
|
|
|
|
textOutlineWidth = 2, -- Width of text-outline pen
|
|
nodeItemHeight = 32, -- Height of treeview nodes
|
|
|
|
viewportSelClr, windowColour,
|
|
windowBrush, blackBrush, OuterPen, ThinPen, DottedPen,
|
|
selectBrushes, defaultBrushes, disabledBrushes,
|
|
redBrushes, greenBrushes, yellowBrushes,
|
|
|
|
greyColour = (color 135 135 175),
|
|
|
|
fn colourToDN clr =
|
|
(
|
|
clr = clr as point3
|
|
(dotNetClass "System.Drawing.Color").FromArgb clr[1] clr[2] clr[3]
|
|
),
|
|
fn makeBrush clr =
|
|
(
|
|
dotNetObject "System.Drawing.SolidBrush" (colourToDN clr)
|
|
),
|
|
fn makeBrushPair clr =
|
|
(
|
|
dataPair normal:(makeBrush clr) faded:(makeBrush (clr * 0.7))
|
|
),
|
|
|
|
fn getDefaultMat =
|
|
(
|
|
-- Set up defaultMat:
|
|
if (defaultMat == undefined) do
|
|
(
|
|
defaultMat = Rage_Shader name:"DefaultLOD"
|
|
RstSetShaderName defaultMat "default"
|
|
|
|
local subTexMap = bitmapTexture()
|
|
setSubTexmap defaultMat 1 subTexMap
|
|
subTexMap.filename = lodColourFile
|
|
)
|
|
|
|
local copyMat = copy defaultMat
|
|
copyMat.name = defaultMat.name
|
|
|
|
return copyMat
|
|
),
|
|
|
|
fn init =
|
|
(
|
|
dataVersion = (VersionNum as integer)
|
|
versionString = (" [v" + (VersionNum as string) + ":" + versionName + "]")
|
|
|
|
-- Ensure that residentPath exists:
|
|
RsMakeSurePathExists residentPath
|
|
|
|
maxLodRadius = maxLodWidth / 2
|
|
|
|
-- Define text-outline pen:
|
|
OuterPen = dotNetObject "System.Drawing.Pen" (dotNetClass "System.Drawing.Color").Black
|
|
--OuterPen.Alignment = OuterPen.Alignment.Outset
|
|
OuterPen.Width = textOutlineWidth
|
|
|
|
ThinPen = dotNetObject "System.Drawing.Pen" (dotNetClass "System.Drawing.Color").Black
|
|
ThinPen.Width = 1
|
|
|
|
DottedPen = dotNetObject "System.Drawing.Pen" (dotNetClass "System.Drawing.Color").Black
|
|
DottedPen.DashStyle = DottedPen.DashStyle.Dash
|
|
|
|
-- Default window-back colour:
|
|
local windowClr = (colorMan.getColor #window) * 255
|
|
windowBrush = makeBrush windowClr
|
|
windowColour = colourToDN windowClr
|
|
|
|
blackBrush = dotNetObject "System.Drawing.SolidBrush" (dotNetClass "System.Drawing.Color").Black
|
|
|
|
-- Default window-text colour:
|
|
--local textClr = (colorMan.getColor #windowText) * 255
|
|
|
|
-- White text by default (it'll be outlined, so should always be visible)
|
|
local defaultTextClr = white
|
|
|
|
-- Selection-box brushes:
|
|
local selColour = (dotNetClass "System.Drawing.Color").fromKnownColor (dotNetClass "system.Drawing.KnownColor").MenuHighlight
|
|
local selClr = (color selColour.r selColour.g selColour.b)
|
|
local halfSelClr = (windowClr + windowClr + selClr) / 3
|
|
|
|
-- Make viewport select-colour lighter:
|
|
viewportSelClr = selClr + ((white - selClr) * 0.65)
|
|
|
|
selectBrushes = dataPair normal:(makeBrush selClr) faded:(makeBrush halfSelClr)
|
|
defaultBrushes = makeBrushPair white
|
|
greenBrushes = makeBrushPair green
|
|
redBrushes = makeBrushPair red
|
|
yellowBrushes = makeBrushPair yellow
|
|
disabledBrushes = makeBrushPair greyColour
|
|
)
|
|
)
|
|
gRsLodCombinerVals = RsLodCombinerVals()
|
|
gRsLodCombinerVals.init()
|
|
|
|
-- Clear existing RsRef combolod-related data-structs, ready for reload:
|
|
-- (deactivated, used during script-development)
|
|
if FALSE do
|
|
(
|
|
RsRefData.refs.comboLod = undefined
|
|
|
|
for ref in RsRefData.refs do
|
|
(
|
|
-- Force slod-ref data to be reloaded:
|
|
for slodLevelList in ref.slodRefs do
|
|
(
|
|
slodLevelList.comboLod = undefined
|
|
slodLevelList.meshObj = undefined
|
|
slodLevelList.material = undefined
|
|
slodLevelList.tintMeshObjs = undefined
|
|
slodLevelList.meshTime = 0
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Struct for storing '.ComboLod' data per RsRef ref-definition:
|
|
struct RsLodCombinerRefData
|
|
(
|
|
maxFaceSize = 0, -- Mesh's maximum billboard-size
|
|
maxBoxSize = 0, -- Mesh's maximum bounding-box length
|
|
|
|
dataHash = 0, -- Stores hash of current data
|
|
hasBBs = False, -- True if ref's mesh uses billboards
|
|
hasTiltAlt = False, -- True if ref has any Tiltable Alternative mesh
|
|
maxTiltAltLevel = 0, -- Max lod-level to use for prop if hasTiltAlt is true
|
|
paletteHash = 0, -- Changes when palette-file changes
|
|
lodDistance,
|
|
tintingDone = False,
|
|
|
|
CanComboLod = True, -- Set to 'False' if prop is invalid for some reason.
|
|
HasAnim = False, -- 'True' if prop is animated (unless it has 'Can ComboLod Animation' flag set)
|
|
HasMissingModel = False, -- 'True' if prop has model(s) defined in xmls but object is missing from maxfile
|
|
|
|
isTree = False -- True if prop's slodrefs include billboards
|
|
)
|
|
|
|
-- Structure used to store tree/node data on tree-nodes:
|
|
-- Also includes functions shared between node-types
|
|
struct RsLodCombinerNodeTag
|
|
(
|
|
Private
|
|
_data, -- Type-specific data-structure
|
|
|
|
Public
|
|
type = #none,
|
|
|
|
tagIdx, -- Item's dataTag index, as collected in RsLodCombinerTool.dataTags
|
|
childTagIdxs = #{},
|
|
selected = False, -- Item is selected in tree
|
|
viewShow = True, -- Item should be drawn in viewport
|
|
isHidden = False, -- Item's sub-objects are all hidden
|
|
|
|
nameText = "", smallText,
|
|
|
|
statusHash = 0, -- Changes whenever an object's data changes
|
|
|
|
-- Default text-values:
|
|
textStyle = 0,
|
|
textSize = 13,
|
|
|
|
statusBrush, subStatusBrush, textBrush, treeNode,
|
|
viewportColour, viewportSelColour,
|
|
|
|
-- Accessor-function for private value:
|
|
fn GetData = (return _data),
|
|
fn SetData newData = (_data = newData),
|
|
|
|
fn UpdateStatus updateObjVals:True refreshObjVals:False \
|
|
showLodDists:True showLodCounts:True showPropCounts:True showChildCounts:True show1to1Counts:True show2to1Counts:True =
|
|
(
|
|
-- This may be changed by later steps:
|
|
textBrush = gRsLodCombinerVals.defaultBrushes.faded
|
|
|
|
local textStream = stringStream ""
|
|
smallText = ""
|
|
|
|
local data = this.GetData()
|
|
if (data != undefined) do
|
|
(
|
|
--format "[xxx:%] " data.genLodLevel to:textStream
|
|
|
|
case type of
|
|
(
|
|
#container:
|
|
(
|
|
-- Unpack from paramblock, if necessary
|
|
if (data.GetGroupList() == undefined) do
|
|
data.Restore()
|
|
|
|
-- Recursively update values for map's comboLod hierarchy
|
|
if updateObjVals do
|
|
data.UpdateVals refreshObjVals:refreshObjVals
|
|
)
|
|
#group:
|
|
(
|
|
format "[BlockType: %] " (toUpper data.childBlockType) to:textStream
|
|
|
|
local isLodBlock = (data.genLodLevel == 2)
|
|
)
|
|
#block:
|
|
(
|
|
-- Show prop-name for LOD-blocks, if blockByProp is set:
|
|
if data.blockByProp and (data.blockByPropName != undefined) do
|
|
(
|
|
format "[%] " (data.blockByPropName as string) to:textStream
|
|
)
|
|
|
|
if data.isResidentBlock do
|
|
(
|
|
format "[MEM-RESIDENT (lod-dist idx: %)] " data.lodDistIdx to:textStream
|
|
)
|
|
)
|
|
)
|
|
|
|
if RsComboLodDebug do
|
|
(
|
|
if (isProperty data #GUID) do
|
|
(
|
|
format "[%] " data.guid to:textStream
|
|
)
|
|
if (isProperty data #treeHash) do
|
|
(
|
|
format "[HASH: %] " data.treeHash to:textStream
|
|
)
|
|
)
|
|
|
|
if (type != #object) do
|
|
(
|
|
local GrpCount = (Data.GetGroupList()).Count
|
|
|
|
if showChildCounts and (GrpCount != 0) do
|
|
(
|
|
format "[Child-Nodes: %] " GrpCount to:textStream
|
|
)
|
|
if showPropCounts do
|
|
(
|
|
format "[Props: %] " data.objCount to:textStream
|
|
)
|
|
if showLodCounts do
|
|
(
|
|
format "[LODs: %] " data.lodObjCount to:textStream
|
|
)
|
|
if show1to1Counts do
|
|
(
|
|
local thisCount = (data.GetPropsPerLodCounts())[1]
|
|
if (thisCount == undefined) do (thisCount = 0)
|
|
format "[1:1 LODs:%] " thisCount to:textStream
|
|
)
|
|
if show2to1Counts do
|
|
(
|
|
local thisCount = (data.GetPropsPerLodCounts())[2]
|
|
if (thisCount == undefined) do (thisCount = 0)
|
|
format "[2:1 LODs:%] " thisCount to:textStream
|
|
)
|
|
)
|
|
|
|
-- Only add newline if some text has already been added:
|
|
if (filePos textStream) != 0 do
|
|
(
|
|
format "\n" to:textStream
|
|
)
|
|
|
|
smallText += (textStream as string)
|
|
)
|
|
|
|
case type of
|
|
(
|
|
#container:
|
|
(
|
|
--textStyle = 2 -- italics
|
|
textSize = 13
|
|
)
|
|
#group:
|
|
(
|
|
-- Show red text if IPL Group has too many props
|
|
if (data.iplObjCount != 0) and (data.iplObjCount > gRsLodCombinerVals.maxIplCount) then
|
|
textBrush = gRsLodCombinerVals.redBrushes.normal
|
|
else
|
|
textBrush = gRsLodCombinerVals.defaultBrushes.normal
|
|
|
|
textStyle = 0 -- normal
|
|
textSize = 13
|
|
|
|
nameText = data.showName()
|
|
)
|
|
#block:
|
|
(
|
|
--textStyle = 2 -- italics
|
|
textSize = 13
|
|
|
|
nameText = data.showName()
|
|
)
|
|
#object:
|
|
(
|
|
textStyle = 1 -- Bold
|
|
textSize = 10
|
|
nameText = data.node.name
|
|
|
|
if (not data.hasValidRef) then
|
|
(
|
|
-- Show undefined refs in red:
|
|
textBrush = gRsLodCombinerVals.redBrushes.normal
|
|
format "[undefined ref!] " to:textStream
|
|
)
|
|
else
|
|
(
|
|
if data.isResidentProp do
|
|
(
|
|
data.getResidentRefNums()
|
|
|
|
local reason = (data.GetResidentReasons())[data.refNum]
|
|
|
|
format "[MEM-RESIDENT: %] " reason to:textStream
|
|
)
|
|
|
|
if (data.dontExport) do
|
|
format "[\"Dont Export\" set] " to:textStream
|
|
|
|
if (data.dontAddIPL) do
|
|
format "[\"Don't Add To IPL\" set] " to:textStream
|
|
|
|
if (data.ignoreMe) do
|
|
format "[\"Ignored By ComboLodder\" set] " to:textStream
|
|
|
|
if (data.doUnTilt) do
|
|
format "['Use Full Matrix' NOT SET - LOD will be untilted] " to:textStream
|
|
|
|
if (data.IPLGroup != "undefined") do
|
|
format "[IPL:%] " data.IPLGroup to:textStream
|
|
|
|
if (data.lowPriority) do
|
|
format "[Low Priority] " to:textStream
|
|
if (data.nonZeroPriority) do
|
|
format "[Non-zero Priority] " to:textStream
|
|
|
|
if (data.useTiltAlt) do
|
|
format "[Using Tiltable mesh] " to:textStream
|
|
|
|
if (data.hasSmallFaces.numberSet != 0) do
|
|
format "[Small billboard, SLODs 50\% of time] " to:textStream
|
|
|
|
case of
|
|
(
|
|
-- Reasons for not lodding prop:
|
|
(data.hasMissingModel):
|
|
(
|
|
format "[Missing meshes: xml/maxfile mismatch?] " to:textStream
|
|
)
|
|
(data.tooSmallToShow):
|
|
(
|
|
local smallBBoxLevel = data.smallBBoxLevel
|
|
format "[Bounds too small for % (<%m)] " gRsLodCombinerVals.lodSuffixes[smallBBoxLevel] gRsLodCombinerVals.minBoxSizes[smallBBoxLevel] to:textStream
|
|
)
|
|
(data.hasAnim):
|
|
(
|
|
format "[has animation] " to:textStream
|
|
)
|
|
(data.sourceLodLevels == 0):
|
|
(
|
|
format "[no LOD-source] " to:textStream
|
|
)
|
|
|
|
-- Show lod-suffixes available for this prop:
|
|
Default:
|
|
(
|
|
local lodSuffixes = gRsLodCombinerVals.lodSuffixes
|
|
|
|
for n = 1 to data.sourceLodLevels do
|
|
(
|
|
format "[%] " lodSuffixes[n] to:textStream
|
|
)
|
|
)
|
|
)
|
|
|
|
if (data.canLod) do
|
|
(
|
|
textBrush = gRsLodCombinerVals.defaultBrushes.normal
|
|
)
|
|
)
|
|
)
|
|
default:()
|
|
)
|
|
|
|
if (type != #object) do
|
|
(
|
|
if (data == undefined) or (data.genLodLevel == 0) then
|
|
(
|
|
statusBrush = gRsLodCombinerVals.disabledBrushes.normal
|
|
|
|
if (type == #container) then
|
|
(
|
|
case of
|
|
(
|
|
((data == undefined) or (data.lodsSynced == Undefined)):
|
|
(
|
|
format "[Contains no ComboLOD groups] " to:textStream
|
|
viewportColour = gRsLodCombinerVals.greyColour
|
|
)
|
|
(data.lodsSynced == True):
|
|
(
|
|
format "[LOD-objects are up-to-date] " to:textStream
|
|
viewportColour = green
|
|
)
|
|
Default:
|
|
(
|
|
format "[Contains out-of-date LOD-objects] " to:textStream
|
|
viewportColour = red
|
|
)
|
|
)
|
|
)
|
|
)
|
|
else
|
|
(
|
|
if (data.dummySlod) do
|
|
(
|
|
format "[Dummy-SLOD] " to:textStream
|
|
)
|
|
|
|
local iplName = data.blockIPLName
|
|
|
|
if (iplName != undefined) and (iplName != "undefined") do
|
|
(
|
|
if (iplName == #Multiple) do
|
|
iplName = "MULTIPLE!"
|
|
|
|
local iplTooBig = (data.iplObjCount > gRsLodCombinerVals.maxIplCount)
|
|
local iplWarningStr = if iplTooBig then " !!! " else ""
|
|
|
|
Format "[%IPL:%" iplWarningStr iplName to:textStream
|
|
|
|
-- Print the number of objects in this IPL if it's too big
|
|
if iplTooBig do
|
|
Format " - % objs in IPL! (>%)" data.iplObjCount gRsLodCombinerVals.maxIplCount to:textStream
|
|
|
|
Format "] " iplWarningStr to:textStream
|
|
)
|
|
|
|
case of
|
|
(
|
|
(data.lodSynced == Undefined):
|
|
(
|
|
statusBrush = gRsLodCombinerVals.disabledBrushes.normal
|
|
viewportColour = gRsLodCombinerVals.greyColour
|
|
|
|
-- Don't bother showing this for the SLOD2 level
|
|
if (data.genLodLevel != 3) do
|
|
Format "[No ComboLOD props for lod-level: %] " data.genLodLevel to:textStream
|
|
)
|
|
(data.lodSynced == True):
|
|
(
|
|
statusBrush = gRsLodCombinerVals.greenBrushes.normal
|
|
format "[LOD-objects are up-to-date] " to:textStream
|
|
viewportColour = green
|
|
)
|
|
(data.hasLod):
|
|
(
|
|
statusBrush = gRsLodCombinerVals.yellowBrushes.normal
|
|
format "[LOD-objects out-of-date] " to:textStream
|
|
viewportColour = yellow
|
|
)
|
|
Default:
|
|
(
|
|
statusBrush = gRsLodCombinerVals.redBrushes.normal
|
|
format "[LOD-object not generated] " to:textStream
|
|
viewportColour = red
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Set sub-node status-circle colour:
|
|
if (data != undefined) and (type != #block) do
|
|
(
|
|
subStatusBrush = case of
|
|
(
|
|
(data.lodsSynced == Undefined):
|
|
(
|
|
gRsLodCombinerVals.disabledBrushes.normal
|
|
)
|
|
(data.lodsSynced == True):
|
|
(
|
|
gRsLodCombinerVals.greenBrushes.normal
|
|
)
|
|
Default:
|
|
(
|
|
gRsLodCombinerVals.redBrushes.normal
|
|
)
|
|
)
|
|
)
|
|
|
|
if showLodDists and (data != undefined) and (data.genLodLevel != 0) do
|
|
(
|
|
local lodObjs = data.getLodObjs recurse:false
|
|
|
|
if (lodObjs.count != 0) do
|
|
(
|
|
local lodDists = for obj in lodObjs collect (getattr obj idxLodDistance)
|
|
local childLodDists = for obj in lodObjs collect (getattr obj idxChildLodDistance)
|
|
|
|
format "[LOD dist%: " (if lodDists.count == 1 then "" else "s") to:textStream
|
|
for n = 1 to lodDists.count do
|
|
(
|
|
format "%m" lodDists[n] to:textStream
|
|
if (n != lodDists.count) do
|
|
(
|
|
format ", " to:textStream
|
|
)
|
|
)
|
|
format "] " to:textStream
|
|
|
|
format "[Child LOD dist%: " (if childLodDists.count == 1 then "" else "s") to:textStream
|
|
for n = 1 to lodDists.count do
|
|
(
|
|
format "%m" childLodDists[n] to:textStream
|
|
if (n != childLodDists.count) do
|
|
(
|
|
format ", " to:textStream
|
|
)
|
|
)
|
|
format "] " to:textStream
|
|
)
|
|
)
|
|
|
|
format "\n" to:textStream
|
|
)
|
|
|
|
if (viewportColour != undefined) do
|
|
(
|
|
viewportSelColour = viewportColour + (0.4 * (white - viewportColour))
|
|
)
|
|
|
|
smallText = (textStream as string)
|
|
|
|
if (type == #object) do
|
|
(
|
|
statusHash = getHashValue smallText 1
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Structure used to define data for LodCombiner ingredient-props and combined-lod objects:
|
|
struct RsLodCombinerObject
|
|
(
|
|
Private
|
|
_parentData, -- Object's parent block/group struct
|
|
_residentReasons,
|
|
|
|
Public
|
|
tagIdx, -- Object's dataTag index, as collected in RsLodCombinerTool.dataTags
|
|
gridParentIdxList, -- Idx-list of grid-parent nodes, used for reblocking objects
|
|
|
|
node, lodDistance,
|
|
sourceHash = 0,
|
|
treeHash = 0,
|
|
refHash = 0,
|
|
refNum = 0,
|
|
|
|
lodLevel = 0, -- Used to denote lod-level of combined-LOD meshes.
|
|
|
|
sourceLodLevels = 0, -- Used to denote number of source-lod meshes a prop has.
|
|
lodLevelsUsed,
|
|
lodLevelsCount, -- Numbers of props available for each lod-level: #([none],[lod],[slod],[slod2])
|
|
|
|
genLodLevel = 0, -- Objects don't generate any lod-objects on their own
|
|
|
|
hasValidRef = false, -- True if object has a valid refDef
|
|
hasSmallFaces = #{}, -- Set if any of object's slod-meshes are too small to always be shown
|
|
smallBBoxLevel = 0, -- Set if an object's bounding-box is too small for it to be shown for particular lod-levels
|
|
tooSmallToShow = false, -- True if prop is too small to add to Lod, but is otherwise valid
|
|
isTree = false, -- True if prop has billboards
|
|
|
|
isResidentProp = false, -- True if prop's model has been marked for being made Memory Resident
|
|
residentRefNums,
|
|
residentDistLimits,
|
|
lodDistIdx = 0,
|
|
|
|
hasAnim = false,
|
|
hasMissingModel = false,
|
|
lowPriority = false, -- True if object has "Low Priority" flag
|
|
nonZeroPriority = false, -- True if object's Priority isn't 0
|
|
canLod = false, -- True if object is suitable LOD-source object
|
|
useTiltAlt = False, -- True if item's node has been rotated more than gRsLodCombinerVals.tiltAltAngle
|
|
|
|
doUnTilt = False, -- True if item's node is slightly tilted, but prop will be shown as untilted ingame (due to not having Full Matrix Rotation)
|
|
|
|
propLodDist,
|
|
ignoreMe = false, -- True if "Ignored By ComboLodder" is set
|
|
dontExport = false, -- True if "Dont Export" is set
|
|
dontAddIPL = false, -- True if "Dont Add To IPL" is set
|
|
iplGroup = "undefined", -- Set to node's "IPL Group"
|
|
AOMult, artAOMult,
|
|
|
|
attrFlagged,
|
|
genWaterReflect = False,
|
|
aoMultEnable = False,
|
|
|
|
tintIdx = 0, -- Object's tintIdx attribute value, if ref has palettes
|
|
|
|
expanded = false, -- Stores state of treeview node
|
|
|
|
-- Accessor-functions for private values:
|
|
fn GetParentData = (return _parentData),
|
|
fn SetParentData newParentData =
|
|
(
|
|
_parentData = newParentData
|
|
),
|
|
|
|
fn GetResidentReasons = (return _residentReasons),
|
|
|
|
-- Get this object's top-level parent:
|
|
fn GetRootData =
|
|
(
|
|
return (_parentData.GetRootData())
|
|
),
|
|
|
|
-- Returns object (as list for cross-compatibility with the other hierarchy-data structs)
|
|
fn getObjs info:False =
|
|
(
|
|
if info then #(This) else #(node)
|
|
),
|
|
|
|
-- Get this object's lodDistance value:
|
|
fn getLodDistance =
|
|
(
|
|
-- Default value for invalid stuff:
|
|
local retVal = 40
|
|
|
|
if (isValidNode node) and (node.refDef != undefined) do
|
|
(
|
|
local refDef = node.refDef
|
|
|
|
if (refDef.comboLod == undefined) or (refDef.comboLod.lodDistance == undefined) do
|
|
(
|
|
refDef.comboLod = undefined
|
|
gRsComboLodFuncs.loadSlodData #(refDef)
|
|
)
|
|
|
|
retVal = refDef.comboLod.lodDistance
|
|
retVal *= node.scale.z
|
|
)
|
|
|
|
return retVal
|
|
),
|
|
|
|
-- Lod-mesh will have this transform - this will be untilted from original if "unTilt" flag has been set
|
|
fn lodXform =
|
|
(
|
|
local retVal = node.transform
|
|
|
|
-- Return untilted transform for non-Full Matrix props:
|
|
if doUnTilt do
|
|
(
|
|
local objRot = retVal.rotationPart
|
|
local untiltedRot = (quat 0 0 objRot.Z objRot.W)
|
|
|
|
retVal = preRotate retVal (inverse objRot)
|
|
retVal = preRotate retVal (untiltedRot)
|
|
)
|
|
|
|
return retVal
|
|
),
|
|
|
|
-- Generates number used to find out when a LOD-source prop has changed:
|
|
fn getSourceHash getFresh:true =
|
|
(
|
|
if not getFresh do return sourceHash
|
|
|
|
if (not canLod) or (not hasValidRef) or (tooSmallToShow) or (not isValidNode node) do return 0
|
|
|
|
local refLodData = node.refDef.comboLod
|
|
|
|
local vals = stringStream ""
|
|
format "%" refLodData.dataHash to:vals
|
|
|
|
if (iplGroup != undefined) and (iplGroup != "undefined") do
|
|
(
|
|
format "#IPL:%" iplGroup to:vals
|
|
)
|
|
|
|
format "#%#%#%#%#%#%" propLodDist AOMult artAOMult (lodXform()) useTiltAlt tintIdx to:vals
|
|
|
|
--if genWaterReflect do (format "#WR" to:vals) -- Doesn't require regenerate to correct this
|
|
if aoMultEnable do (format "#AOM" to:vals)
|
|
|
|
return (getHashValue (vals as string) 1)
|
|
),
|
|
|
|
-- Object LodLevelCounts are combined for the parent-struct's values:
|
|
-- (used to properly update progress-bar during lod-generation)
|
|
fn SetLodLevelsCount =
|
|
(
|
|
this.lodLevelsUsed = #{}
|
|
this.lodLevelsCount = for n = 1 to 4 collect 0
|
|
|
|
if (this.sourceLodLevels == 0) then
|
|
(
|
|
-- Index 1 is for props that are missing lods
|
|
this.lodLevelsCount[1] = 1
|
|
)
|
|
else
|
|
(
|
|
for n = 1 to this.sourceLodLevels do
|
|
(
|
|
this.lodLevelsCount[n + 1] = 1
|
|
this.lodLevelsUsed[n] = True
|
|
)
|
|
)
|
|
|
|
return OK
|
|
),
|
|
|
|
-- Returns list of map's Memory Resident object-refs indexes, referenced from map-node's bitarray:
|
|
fn getResidentRefNums =
|
|
(
|
|
-- Set up references:
|
|
if (residentRefNums == undefined) do
|
|
(
|
|
residentRefNums = _parentData.GetResidentRefNums()
|
|
_residentReasons = _parentData.GetResidentReasons()
|
|
residentDistLimits = _parentData.residentDistLimits
|
|
)
|
|
|
|
return residentRefNums
|
|
),
|
|
|
|
-- Update values to match this object's current state:
|
|
fn UpdateVals =
|
|
(
|
|
sourceHash = 0
|
|
sourceLodLevels = 0
|
|
canLod = False
|
|
|
|
hasValidRef = false
|
|
lowPriority = false
|
|
nonZeroPriority = false
|
|
dontExport = false
|
|
dontAddIPL = false
|
|
ignoreMe = false
|
|
hasSmallFaces = #{}
|
|
smallBBoxLevel = 0
|
|
tooSmallToShow = false
|
|
hasAnim = false
|
|
hasMissingModel = false
|
|
useTiltAlt = False
|
|
doUnTilt = False
|
|
tintIdx = 0
|
|
refNum = 0
|
|
attrFlagged = False
|
|
isTree = False
|
|
|
|
AOMult = gRsLodCombinerVals.natAOMult
|
|
artAOMult = gRsLodCombinerVals.artAOMult
|
|
|
|
if (isProperty node #refDef) do
|
|
(
|
|
lowPriority = (getAttr node idxLowPriority)
|
|
nonZeroPriority = ((getAttr node idxPriority) != 0)
|
|
dontExport = (getAttr node idxDontExport)
|
|
dontAddIPL = (getAttr node idxDontAddToIpl)
|
|
ignoreMe = (idxIgnoredByComboLodder != undefined) and (getAttr node idxIgnoredByComboLodder)
|
|
canLod = not (ignoreMe or dontExport or dontAddIPL or lowPriority or nonZeroPriority)
|
|
|
|
propLodDist = getAttr node idxLodDistance
|
|
|
|
iplGroup = getAttr node idxIPLGroup
|
|
|
|
genWaterReflect = ((idxGenerateWaterReflectiveLODs != undefined) and (getAttr node idxGenerateWaterReflectiveLODs))
|
|
--aoMultEnable = getAttr node idxEnableAmbientMultiplier -- Not currently used; we always set True for trees, False otherwise:
|
|
attrFlagged = (genWaterReflect or aoMultEnable)
|
|
|
|
local refDef = node.refDef
|
|
hasValidRef = (refDef != undefined)
|
|
|
|
if hasValidRef do
|
|
(
|
|
refNum = refDef.num
|
|
|
|
isResidentProp = (getResidentRefNums())[refNum]
|
|
|
|
local refData = refDef.comboLod
|
|
|
|
-- Don't use objects with 'CanComboLod' set to 'False':
|
|
CanLod = CanLod and RefData.CanComboLod
|
|
|
|
-- This prop's model has billboards:
|
|
isTree = refData.isTree
|
|
|
|
-- Collect flags explaining reasons why prop isn't lodding:
|
|
HasMissingModel = refData.HasMissingModel
|
|
HasAnim = refData.HasAnim
|
|
|
|
if (CanLod) do
|
|
(
|
|
-- Lod-meshes will be automatically untilted on generation, if their props aren't going to be tilted in-game:
|
|
local hasFullMatrix = ((refDef.isDynamic) or (getAttr node idxUseFullMatrix) or (RsHasAutoFullMatrix node))
|
|
|
|
-- Can/should this prop use a Tiltable Alternative mesh?
|
|
if (hasFullMatrix) then
|
|
(
|
|
if (refData.hasTiltAlt) do
|
|
(
|
|
local tiltAngle = RsGetObjTilt node
|
|
useTiltAlt = (tiltAngle > gRsLodCombinerVals.tiltAltAngle)
|
|
)
|
|
)
|
|
else
|
|
(
|
|
-- Set non-full-matrix props to be untilted, if they are tilted:
|
|
local objRot = node.rotation
|
|
doUnTilt = ((objRot.X != 0) or (objRot.Y != 0))
|
|
)
|
|
|
|
-- If this object has non-default tinting, get its tint-index attribute:
|
|
if (refData.paletteHash != 0) and (refDef.paletteCount > 1) do
|
|
(
|
|
tintIdx = getAttr node idxTint
|
|
)
|
|
|
|
local slodRefs = node.refDef.slodRefs
|
|
hasSmallFaces.count = slodRefs.count
|
|
|
|
local maxLodLevel = if useTiltAlt then refData.maxTiltAltLevel else slodRefs.count
|
|
|
|
-- Stop counting lod-meshes if any slodRefs have invalid meshObj (failed to load mesh from Maxfile)
|
|
local validMeshes = True
|
|
|
|
local minBoxSizes = gRsLodCombinerVals.minBoxSizes
|
|
local minFaceSize = gRsLodCombinerVals.minFaceSize
|
|
local nodeScale = node.scale.Z
|
|
|
|
for lodLevel = 1 to maxLodLevel while validMeshes do
|
|
(
|
|
-- Don't bother checking tiltable-alternative slodRef for size...
|
|
local slodRef = slodRefs[lodLevel][1]
|
|
validMeshes = (slodRef.meshObj != undefined)
|
|
|
|
if validMeshes then
|
|
(
|
|
local slodRefData = slodRef.comboLod
|
|
|
|
if ((refData.maxBoxSize * nodeScale) < minBoxSizes[lodLevel]) then
|
|
(
|
|
-- Don't add for this lod-level if prop's bounding-box is too small
|
|
smallBBoxLevel = lodLevel
|
|
validMeshes = False
|
|
|
|
if (lodLevel == 1) do (tooSmallToShow = True)
|
|
)
|
|
else
|
|
(
|
|
sourceLodLevels += 1
|
|
|
|
-- Don't always use slodrefs if their billboards scale down too small (ignore if they don't have billboards)
|
|
if (lodLevel > 1) and (slodRefData.maxFaceSize != 0) and ((slodRefData.maxFaceSize * nodeScale) < minFaceSize) do
|
|
(
|
|
hasSmallFaces[lodLevel] = True
|
|
)
|
|
)
|
|
)
|
|
else
|
|
(
|
|
-- We shouldn't find ourselves here, meshes -should- have already been checked during 'fn loadSlodData':
|
|
format "MISSING LOD-SOURCE MODEL: %\n" slodRef.name
|
|
hasMissingModel = True
|
|
canLod = False
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
canLod = canLod and (sourceLodLevels != 0)
|
|
|
|
sourceHash = getSourceHash getFresh:true
|
|
refHash = if (sourceHash == 0) then 0 else refDef.comboLod.dataHash
|
|
)
|
|
|
|
treeHash = sourceHash
|
|
|
|
-- Set object-counts, used to properly update progress-bar during lod-generation:
|
|
this.SetLodLevelsCount()
|
|
),
|
|
|
|
-- Create struct-instance based on obj:
|
|
fn create obj parentData:undefined =
|
|
(
|
|
local objInfo = RsLodCombinerObject node:obj
|
|
ObjInfo.SetParentData ParentData
|
|
objInfo.UpdateVals()
|
|
|
|
return objInfo
|
|
),
|
|
|
|
-- Store object to xml, if node is valid:
|
|
fn store xmlWriter objsList:#() =
|
|
(
|
|
if (isValidNode node) do
|
|
(
|
|
xmlWriter.WriteStartElement "object"
|
|
|
|
-- Add node to object-list, and store its index:
|
|
append objsList node
|
|
xmlWriter.WriteAttributeString "objNum" (objsList.count as string)
|
|
|
|
-- Write attributes:
|
|
for item in
|
|
#(
|
|
dataPair name:"sourceHash" val:sourceHash,
|
|
dataPair name:"lodLevel" val:lodLevel
|
|
)
|
|
where (item.val != 0) do
|
|
(
|
|
xmlWriter.WriteAttributeString item.name (item.val as string)
|
|
)
|
|
|
|
xmlWriter.WriteEndElement()
|
|
)
|
|
|
|
return OK
|
|
),
|
|
|
|
-- Reconstitute object-struct from stored xml element:
|
|
fn Restore xmlReader objsList:#() parentData:Undefined readVersion:0 =
|
|
(
|
|
local objNum = xmlReader.Item["objNum"]
|
|
if (objNum == undefined) do return dontCollect
|
|
|
|
-- Retrieve indexed object from list:
|
|
local getObj = objsList[objNum as integer]
|
|
|
|
-- Cancel object-import if object wasn't found...
|
|
if (not isValidNode getObj) do return dontCollect
|
|
|
|
-- If obj was found, generate new struct:
|
|
local restoredObj = RsLodCombinerObject.create getObj parentData:parentData
|
|
|
|
-- LodLevel is set to lod-level a LOD-object was generated for.
|
|
-- Set to 0 for props (default)
|
|
-- Set to -1 for containerLOD proxies
|
|
local lodLevel = xmlReader.Item["lodLevel"]
|
|
if (lodLevel != undefined) do
|
|
(
|
|
restoredObj.lodLevel = lodLevel as integer
|
|
)
|
|
|
|
lodLevel = restoredObj.lodLevel
|
|
|
|
-- Abort if object-class doesn't match lodLevel value:
|
|
case of
|
|
(
|
|
(lodLevel == -1):
|
|
(
|
|
-- ContainerLods:
|
|
if not (RsIsContainerLod getObj) do return dontCollect
|
|
)
|
|
(lodLevel == 0):
|
|
(
|
|
-- Props:
|
|
if not (isRsRef getObj) do return dontCollect
|
|
)
|
|
(lodLevel > 0):
|
|
(
|
|
if not (isEditPolyMesh getObj) do return dontCollect
|
|
|
|
-- Only bother retrieving sourceHash for generated combined-mesh objects:
|
|
local sourceHash = xmlReader.Item["sourceHash"]
|
|
if (sourceHash != undefined) do
|
|
(
|
|
restoredObj.sourceHash = sourceHash as integer
|
|
)
|
|
)
|
|
)
|
|
|
|
return restoredObj
|
|
),
|
|
|
|
-- Used by viewport-draw callback to draw object's overlays:
|
|
fn viewportDraw colour:white showBoxes:True showShapes:True =
|
|
(
|
|
--gw.wMarker (gw.wtransPoint node.pos) #hollowBox color:colour
|
|
)
|
|
)
|
|
|
|
-- Structure used to define LodCombiner groups:
|
|
struct RsLodCombinerGroup
|
|
(
|
|
Private
|
|
_parentData, -- Object's parent block/group struct
|
|
_groupList = #(), -- Subgroups
|
|
_objItems = #(), -- Level-1 nodes contain objects
|
|
|
|
_residentReasons,
|
|
_boundsOutline,
|
|
_propsPerLodCounts = #(), -- Collects number of LOD-blocks lodding different numbers of props
|
|
|
|
Public
|
|
GUID, -- Randomly-generated identifier-value
|
|
groupName,
|
|
childNum = 0, -- Used to generate LOD-block names
|
|
lodPrefixName,
|
|
lodObjName,
|
|
|
|
-- Top-level Lod-objects can be automatically parented to a ContainerLOD object:
|
|
containerLodParent,
|
|
|
|
tagIdx, -- Group's dataTag index, as collected in RsLodCombinerTool.dataTags
|
|
gridParentIdxList, -- Idx-list of grid-parent nodes, used for reblocking objects
|
|
|
|
lodObjItem, -- LOD-object generated for this group
|
|
dummySlod = False, -- Generate dummy-object for level-2 lod-groups with non-slod lods
|
|
|
|
objCount = 0, -- Total number of subobjects
|
|
lodObjCount = 0, -- Total number of generated lod-objects
|
|
|
|
sourceHash = 0, -- Current prop-hash at last check
|
|
treeHash = 0, -- Used to combine object-hashes, without setting to 0 for non-lodding nodes.
|
|
lodObjHash = 0, -- Set to combined prop-hash when lodObjItem is generated
|
|
hasLod, -- Set to true if lodObjItem exists
|
|
|
|
hasTrees = False, -- Group-sources include billboard (tree) props
|
|
dontWaterReflect = True, -- A source-prop has "Generate Water Reflective LODs" ticked
|
|
|
|
lodSynced, -- Set to true if lodObjHash matches sourceHash, false if not. Undefined if LOD isn't required.
|
|
lodsSynced, -- True if this node and its children are all in-sync.
|
|
|
|
genLodLevel, -- This group-node generates this lod-level:
|
|
|
|
lodLevelsUsed,
|
|
lodLevelsCount,
|
|
blockType, blockRadius, blockPos, blockRot, childBlockPos, blockOrigin,
|
|
childBlockType = #Hex, childBlockRadius,
|
|
|
|
blockByProp = True, -- If true, LOD-groups will only contains props with the same name
|
|
blockByPropName, -- If a blockByProp group, group only contains props with this objectname
|
|
blockIPLName, -- We only allow groups to include props with the same "IPL Group" name
|
|
iplObjCount = 0, -- Number of objects that would exist in this IPL if Lods have been generated
|
|
|
|
residentRefNums,
|
|
residentDistLimits,
|
|
isResidentBlock = False, -- True if this is LOD-group used for Memory Resident props
|
|
lodDistIdx = 0, -- Used to split Resident Model lod-blocks up into different LOD-distance ranges.
|
|
|
|
worldNormal = [0,0,1], -- Used to set "Tree_Lod2" shader "World Normal" values; set to undefined if Tree Normals are to be used instead.
|
|
|
|
-- Used to store bounding-box and area-shape info:
|
|
hexQuadTriHeight, triVerts,
|
|
boundsMin, boundsMax, boundsSize,
|
|
|
|
expanded = false,
|
|
|
|
-- This is set to True if an outline-change has been made,
|
|
-- and reblocking still needs to happen
|
|
reblockRequired = False,
|
|
|
|
-- Accessor-functions for private values:
|
|
fn GetGroupList = (return _groupList),
|
|
fn SetGroupList newList = (_groupList = newList),
|
|
fn GetParentData = (return _parentData),
|
|
fn SetParentData newParentData = (_parentData = newParentData),
|
|
fn GetObjItems = (return _objItems),
|
|
fn SetObjItems newList = (_objItems = newList),
|
|
fn GetResidentReasons = (return _residentReasons),
|
|
fn GetBoundsOutline = (return _boundsOutline),
|
|
fn GetPropsPerLodCounts = (return _propsPerLodCounts),
|
|
|
|
-- Get this group's top-level parent
|
|
fn GetRootData =
|
|
(
|
|
return (_parentData.GetRootData())
|
|
),
|
|
|
|
-- Returns list of map's Memory Resident object-refs indexes, referenced from map-node's bitarray:
|
|
fn getResidentRefNums =
|
|
(
|
|
-- Set up references:
|
|
if (residentRefNums == undefined) do
|
|
(
|
|
local slodRoot = this.GetRootData()
|
|
residentRefNums = slodRoot.residentRefNums
|
|
_residentReasons = slodRoot.GetResidentReasons()
|
|
residentDistLimits = slodRoot.residentDistLimits
|
|
)
|
|
|
|
return residentRefNums
|
|
),
|
|
|
|
-- Get list of value-names to copy when cloning a struct
|
|
fn GetClonePropNames =
|
|
(
|
|
local outVal = gRsLodCombinerVals.grpClonePropNames
|
|
|
|
if (outVal != Undefined) do
|
|
return outVal
|
|
|
|
local ignoreNames = #(#LodObjItem, #TriVerts, #ContainerLodParent, #Guid)
|
|
|
|
outVal = #()
|
|
for propName in (GetPropNames this) do
|
|
(
|
|
local val = GetProperty this propName
|
|
|
|
-- Collect non-function values if they're not to be ignored
|
|
if (not IsKindOf val MAXScriptFunction) and (FindItem ignoreNames propname == 0) do
|
|
append outVal propName
|
|
)
|
|
|
|
-- Store the list for further uses
|
|
gRsLodCombinerVals.grpClonePropNames = outVal
|
|
|
|
return outVal
|
|
),
|
|
|
|
-- Makes copy of current LOD-block group-struct, with arrays dereferenced and cleared:
|
|
fn Clone =
|
|
(
|
|
-- Make new Group
|
|
local newGrp = RsLodCombinerGroup guid:(GenGuid())
|
|
|
|
-- Copy most values across
|
|
for propName in (this.GetClonePropNames()) do
|
|
(
|
|
local val = GetProperty this propName
|
|
SetProperty newGrp propName val
|
|
)
|
|
|
|
-- Transfer this private value
|
|
newGrp.SetParentData _parentData
|
|
|
|
return newGrp
|
|
),
|
|
|
|
-- Gets combined list of group's subgroup list-items:
|
|
fn GetSubGroups recurse:false =
|
|
(
|
|
local outList = #()
|
|
Join outList _groupList
|
|
|
|
if recurse do
|
|
(
|
|
for item in _groupList do
|
|
(
|
|
join outList (item.getSubGroups recurse:true)
|
|
)
|
|
)
|
|
|
|
return outList
|
|
),
|
|
|
|
fn UpdateBounds =
|
|
(
|
|
-- Filter out undefined values (these shouldn't happen!)
|
|
_objItems = for item in _objItems collect
|
|
(
|
|
if (item == undefined) then (print "updateBounds: UNDEFINED OBJECT-ITEM!"; dontCollect) else item
|
|
)
|
|
|
|
-- Collect object-positions:
|
|
local minMaxList = for item in _objItems where (isValidNode item.node) collect item.node.pos
|
|
local outlineConsiderList = deepCopy minMaxList
|
|
|
|
-- Add bounds of subgroups to position-list:
|
|
for grp in _groupList do
|
|
(
|
|
if (grp.boundsSize == Undefined) do
|
|
grp.UpdateBounds()
|
|
|
|
if (grp.boundsSize != undefined) do
|
|
(
|
|
append minMaxList grp.boundsMin
|
|
append minMaxList grp.boundsMax
|
|
)
|
|
|
|
local grpOutline = grp.GetBoundsOutline()
|
|
if (grpOutline != Undefined) do
|
|
Join outlineConsiderList grpOutline
|
|
)
|
|
|
|
this.boundsSize = Undefined
|
|
this.boundsMin = Undefined
|
|
this.boundsMax = undefined
|
|
_boundsOutline = Undefined
|
|
|
|
if (outlineConsiderList.count == 0) do
|
|
return undefined
|
|
|
|
RsGetBBox minMaxList &boundsMin &boundsMax
|
|
boundsSize = boundsMax - boundsMin
|
|
|
|
-- Generate bounding-outline:
|
|
--_boundsOutline = #(boundsMin, [boundsMax.X, boundsMin.Y, 0], boundsMax, [boundsMin.X, boundsMax.Y, 0]) -- Bounding-box
|
|
_boundsOutline = RsFindConvexOutline outlineConsiderList
|
|
|
|
local minZ = boundsMin.z
|
|
for pos in _boundsOutline do pos.z = minZ
|
|
|
|
if (blockType == #none) do
|
|
(
|
|
blockPos = boundsMin + (0.5 * boundsSize)
|
|
)
|
|
if (childBlockType == #none) do
|
|
(
|
|
childBlockPos = boundsMin + (0.5 * boundsSize)
|
|
)
|
|
|
|
return boundsSize
|
|
),
|
|
|
|
-- Gets implicitly-set values from parent, if undefined:
|
|
fn getParentVals force:False =
|
|
(
|
|
if force or (blockType == undefined) do
|
|
(
|
|
blockType = _parentData.childBlockType
|
|
blockRadius = _parentData.childBlockRadius
|
|
)
|
|
|
|
if (childBlockRadius == undefined) and (genLodLevel != 1) do
|
|
(
|
|
childBlockRadius = gRsLodCombinerVals.defBlockRadius[genLodLevel]
|
|
)
|
|
),
|
|
|
|
-- Update block-area values, if group is gridded:
|
|
fn setBlockGeom scaleUp:true =
|
|
(
|
|
-- Get implicitly-set values from parent, if undefined:
|
|
getParentVals()
|
|
|
|
if (blockRadius == undefined) do return false
|
|
|
|
case blockType of
|
|
(
|
|
#hex:
|
|
(
|
|
hexQuadTriHeight = blockRadius * cos 30
|
|
)
|
|
#tri:
|
|
(
|
|
local triBaseHeight = blockRadius * sin 30
|
|
local triBaseRadius = blockRadius * cos 30
|
|
|
|
triVerts = #([0,-blockRadius,0], [-triBaseRadius,triBaseHeight,0], [triBaseRadius,triBaseHeight,0])
|
|
|
|
-- Verts are scaled out slightly, to make sure isInBlockArea picks up edge-cases:
|
|
local triXformer = if scaleUp then (scaleMatrix [1.001, 1.001, 1.001]) else (matrix3 1)
|
|
|
|
-- Rotate verts around pivot:
|
|
if (blockRot != undefined) and (blockRot != 0) then
|
|
(
|
|
rotateZ triXformer blockRot
|
|
)
|
|
|
|
triXformer[4] = blockPos
|
|
|
|
-- Transform verts:
|
|
for n = 1 to 3 do
|
|
(
|
|
triVerts[n] *= triXformer
|
|
)
|
|
)
|
|
)
|
|
),
|
|
|
|
-- Get blockByPropName from first object in block:
|
|
fn setBlockPropName =
|
|
(
|
|
if blockByProp and (not isResidentBlock) and (_objItems.count != 0) and (_objItems[1].node != undefined) then
|
|
(
|
|
blockByPropName = toLower _objItems[1].node.objectName
|
|
)
|
|
else
|
|
(
|
|
blockByPropName = undefined
|
|
)
|
|
),
|
|
|
|
-- Find lod-distance index for a prop, used to avoid having Resident blocks containing props with wildly different lod-distances.
|
|
-- propLodDistIdx is index below where (DistLimit > propLodDist)
|
|
getPropLodDistIdx_ref,
|
|
fn getPropLodDistIdx obj =
|
|
(
|
|
-- Set up reference to map-node's function:
|
|
if (getPropLodDistIdx_ref == undefined) do
|
|
(
|
|
local slodRoot = this.GetRootData()
|
|
getPropLodDistIdx_ref = slodRoot.getPropLodDistIdx
|
|
)
|
|
|
|
getPropLodDistIdx_ref obj
|
|
),
|
|
|
|
-- Returns true if a position is within this block's area:
|
|
fn isInBlockArea obj =
|
|
(
|
|
local retVal = true
|
|
|
|
-- These tests are only applied to level-1 lods:
|
|
if (genLodLevel == 1) do
|
|
(
|
|
getResidentRefNums()
|
|
|
|
local isResidentProp = (obj.refDef != undefined) and residentRefNums[obj.refDef.num]
|
|
|
|
case of
|
|
(
|
|
-- Don't add Resident models to non-resident blocks, and vice-versa:
|
|
(isResidentProp != isResidentBlock):
|
|
(
|
|
retVal = false
|
|
)
|
|
-- Only match up Resident props to blocks with matching lod-distance index:
|
|
isResidentProp:
|
|
(
|
|
-- Allow prop into block if lodDistIdx matches:
|
|
retVal = ((getPropLodDistIdx obj) == lodDistIdx)
|
|
)
|
|
-- Check prop-name, if blockByProp is active:
|
|
blockByProp:
|
|
(
|
|
if (blockByPropName == undefined) do
|
|
(
|
|
setBlockPropName()
|
|
)
|
|
|
|
if ((blockByPropName != undefined) and (not matchPattern obj.objectName pattern:blockByPropName)) do
|
|
(
|
|
retVal = false
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
if not retVal do
|
|
return False
|
|
|
|
if ((blockPos == undefined) and (blockType != #none)) do
|
|
this.UpdateBounds()
|
|
|
|
local objPos = obj.pos
|
|
|
|
case blockType of
|
|
(
|
|
#none:()
|
|
#box:
|
|
(
|
|
-- Is point inside box?
|
|
retVal = (objPos.x <= (blockPos.x + blockRadius)) and (objPos.x >= (blockPos.x - blockRadius)) and
|
|
(objPos.y <= (blockPos.y + blockRadius)) and (objPos.y >= (blockPos.y - blockRadius))
|
|
)
|
|
#hex:
|
|
(
|
|
-- Get pos relative to hex, and put it in positive hex-quadrant:
|
|
local qx = abs (objPos.x - blockPos.x)
|
|
local qy = abs (objPos.y - blockPos.y)
|
|
|
|
-- Is point outside hex-quad's bounding-box?
|
|
if (qx > blockRadius) or (qy > hexQuadTriHeight) then
|
|
(
|
|
retVal = False
|
|
)
|
|
else
|
|
(
|
|
-- Is point inside quad?
|
|
retVal = (blockRadius * hexQuadTriHeight) - (hexQuadTriHeight * qx) - (0.5 * blockRadius * qy) >= 0
|
|
)
|
|
)
|
|
#tri:
|
|
(
|
|
-- Is point inside triangle?
|
|
retVal = RSGeom_PointInTriangle triVerts[1] triVerts[2] triVerts[3] objPos
|
|
)
|
|
)
|
|
|
|
return retVal
|
|
),
|
|
|
|
-- Get number of objects for each lod-level (item 1 is for non-lod objects)
|
|
-- (used to properly update progress-bar during lod-generation)
|
|
fn SetLodLevelsCount =
|
|
(
|
|
this.lodLevelsUsed = #{}
|
|
this.lodLevelsCount = for n = 1 to 4 collect 0
|
|
|
|
for itemList in #(this.GetObjItems(), this.GetGroupList()) do
|
|
(
|
|
for n = 1 to itemList.count do
|
|
(
|
|
local thisItem = itemList[n]
|
|
local childLodLevelsUsed = thisItem.lodLevelsUsed
|
|
local childLodLevelsCount = thisItem.lodLevelsCount
|
|
|
|
this.lodLevelsUsed += childLodLevelsUsed
|
|
for n = 1 to 4 do
|
|
this.lodLevelsCount[n] += childLodLevelsCount[n]
|
|
)
|
|
)
|
|
|
|
return this
|
|
),
|
|
|
|
-- Returns list of objects in group:
|
|
fn getObjs lodFilter: recurse:true info:false =
|
|
(
|
|
-- Get group's object-list:
|
|
local retVal = for item in _objItems collect item
|
|
|
|
-- Add subgroups' object-lists:
|
|
if recurse do
|
|
(
|
|
for item in _groupList do
|
|
(
|
|
join retVal (item.getObjs info:true)
|
|
)
|
|
)
|
|
|
|
-- Filter to objects offering meshes for specific LOD-level:
|
|
if (lodFilter != unsupplied) and (lodFilter != false) do
|
|
(
|
|
local filterLevel = if (lodFilter == true) then genLodLevel else lodFilter
|
|
retVal = for item in retVal where (item.canLod) and (item.sourceLodLevels >= filterLevel) collect item
|
|
|
|
-- Filter out half of the small-face slod-sources:
|
|
if (filterLevel > 1) do
|
|
(
|
|
local useThis = True
|
|
retVal = for item in retVal collect
|
|
(
|
|
-- Use every other small-face slod-source:
|
|
if item.hasSmallFaces[filterLevel] then
|
|
(
|
|
if useThis then
|
|
(
|
|
useThis = False
|
|
item
|
|
)
|
|
else
|
|
(
|
|
useThis = True
|
|
DontCollect
|
|
)
|
|
)
|
|
else
|
|
(
|
|
item
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
if not info do
|
|
(
|
|
retVal = for item in retVal collect item.node
|
|
)
|
|
|
|
return retVal
|
|
),
|
|
|
|
-- Returns list of lod-objects generated by group (and its subgroups)
|
|
fn getLodObjs info:false recurse:true =
|
|
(
|
|
local retVal = #(lodObjItem)
|
|
|
|
if recurse do
|
|
(
|
|
for grp in (getSubGroups recurse:true) do
|
|
(
|
|
append retVal grp.lodObjItem
|
|
)
|
|
)
|
|
|
|
retVal = for item in retVal where ((item != undefined) and (isValidNode item.node)) collect item
|
|
|
|
if not info do
|
|
(
|
|
retVal = for item in retVal collect item.node
|
|
)
|
|
|
|
return retVal
|
|
),
|
|
|
|
-- Remove group's lod-objects:
|
|
fn deleteLods recurse:false =
|
|
(
|
|
local grps = #()
|
|
if recurse do
|
|
(
|
|
join grps (getSubGroups recurse:true)
|
|
)
|
|
|
|
-- Delete lod-objects:
|
|
local delObjs = getLodObjs recurse:recurse
|
|
--format "DELETING OBJS: %\n" (delObjs as string)
|
|
undo off
|
|
(
|
|
delete delObjs
|
|
)
|
|
|
|
-- Set groups to unsynced:
|
|
lodObjItem = grps.lodObjItem = undefined
|
|
lodObjHash = grps.lodObjHash = 0
|
|
lodSynced = lodsSynced = grps.lodSynced = grps.lodsSynced = undefined
|
|
|
|
return OK
|
|
),
|
|
|
|
-- Removes this group from its parent-grouplist, and deletes removed-hierarchy's LOD-objects:
|
|
fn DeleteGroup =
|
|
(
|
|
-- Delete group's generated lod-objects:
|
|
deleteLods recurse:true
|
|
|
|
-- Filter this group out from parent-item's groupList:
|
|
local ParentGrpList = _parentData.GetGroupList()
|
|
local FindNum = FindItem ParentGrpList This
|
|
if (FindNum != 0) do
|
|
(
|
|
DeleteItem ParentGrpList FindNum
|
|
)
|
|
),
|
|
|
|
-- Generate combined hash for group's lod-source objects:
|
|
fn getSourceHash getFresh:true srcObjItems: =
|
|
(
|
|
|
|
if (srcObjItems == unsupplied) do
|
|
(
|
|
srcObjItems = getObjs lodFilter:true info:true
|
|
)
|
|
|
|
-- Collect hashes for objects with matching LodLevels:
|
|
local hashes = for item in srcObjItems collect (item.getSourceHash getFresh:getFresh)
|
|
|
|
local retVal = if (hashes.count == 0) then 0 else (getHashValue hashes 1)
|
|
|
|
-- Special case for slods with sub-lods
|
|
this.dummySlod = False
|
|
|
|
-- SLOD is index 3 because lodLevelsCount corresponds to these lod-prop counts #([none],[lod],[slod],[slod2])
|
|
if (retVal == 0) and (genLodLevel == 2) and (lodLevelsCount[3] == 0) do
|
|
(
|
|
-- Do any sub-lods generate lods?
|
|
local noSubLods = True
|
|
for grp in _groupList while noSubLods do
|
|
(
|
|
noSubLods = (grp.sourceHash == 0)
|
|
)
|
|
|
|
-- If some do, this node should generate a dummy-slod:
|
|
if (not noSubLods) do
|
|
(
|
|
this.dummySlod = True
|
|
retVal = (getHashValue #("dummySlod", gRsLodCombinerVals.dataVersion, gRsLodCombinerVals.dummySlodVersion) 1) -- Arbitrary version-linked value for dummy-slod
|
|
)
|
|
)
|
|
|
|
--format "GROUP HASH: %\n" retVal
|
|
|
|
return retVal
|
|
),
|
|
|
|
-- Gets number lod-objects for each props-per-lod-group count...
|
|
-- (used to show 1-prop/2-prop status-text bits)
|
|
fn SetPropsPerLodCounts =
|
|
(
|
|
_propsPerLodCounts = #()
|
|
|
|
local lodPropsCount = 0
|
|
for item in _objItems where (item.sourceHash != 0) do
|
|
(
|
|
lodPropsCount += 1
|
|
)
|
|
|
|
if (lodPropsCount != 0) do
|
|
(
|
|
_propsPerLodCounts[lodPropsCount] = 1
|
|
)
|
|
|
|
for grp in _groupList do
|
|
(
|
|
local grpCounts = grp.GetPropsPerLodCounts()
|
|
for n = 1 to grpCounts.count do
|
|
(
|
|
local thisVal = grpCounts[n]
|
|
|
|
if (thisVal != undefined) do
|
|
(
|
|
if (_propsPerLodCounts[n] == undefined) do
|
|
(
|
|
_propsPerLodCounts[n] = 0
|
|
)
|
|
|
|
_propsPerLodCounts[n] += thisVal
|
|
)
|
|
)
|
|
)
|
|
|
|
return OK
|
|
),
|
|
|
|
-- Update values to match this group's current state:
|
|
fn UpdateVals refreshObjVals:False =
|
|
(
|
|
-- Update block-prop assignment if block-gridding values have changed
|
|
if this.reblockRequired do
|
|
this.ReassignBlocks()
|
|
|
|
-- Generate GUID, if group doesn't already have one:
|
|
if (this.guid == Undefined) do
|
|
this.guid = genGuid()
|
|
|
|
local doPrompt = (groupName != undefined)
|
|
|
|
if doPrompt do
|
|
PushPrompt ("Updating SLOD-group values: " + groupName)
|
|
|
|
-- Initialise implicitly-set values from parent, if undefined
|
|
this.GetParentVals()
|
|
|
|
local subIpls = #()
|
|
local treeHashes = #()
|
|
|
|
-- Filter out deleted/undefined objects, do object-value updates if required:
|
|
_objItems = for item in _objItems where (item != undefined) and (isValidNode item.node) collect
|
|
(
|
|
if refreshObjVals do
|
|
(
|
|
item.UpdateVals()
|
|
)
|
|
|
|
Append subIpls (item.iplGroup as string)
|
|
Append treeHashes item.treeHash
|
|
|
|
item
|
|
)
|
|
objCount = _objItems.count
|
|
|
|
-- Update subgroup values first:
|
|
for item in _groupList do
|
|
(
|
|
item.UpdateVals refreshObjVals:refreshObjVals
|
|
objCount += item.objCount
|
|
|
|
append treeHashes item.treeHash
|
|
|
|
-- Collect 'IPL Group' for group-nodes that generate lods:
|
|
if (item.blockIPLName != Undefined) do
|
|
Append subIpls item.blockIPLName
|
|
)
|
|
|
|
-- Used to track overall changes to sub-nodes:
|
|
this.treeHash = GetHashValue treeHashes 1
|
|
|
|
-- Get props-per-lod counts, used to show 1-prop/2-prop status-text bits:
|
|
this.SetPropsPerLodCounts()
|
|
|
|
-- Set IPL Group text based on group's sub-objects/groups:
|
|
subIpls = makeUniqueArray subIpls
|
|
blockIPLName = case subIpls.count of
|
|
(
|
|
1:(subIpls[1])
|
|
0:(undefined)
|
|
Default:(#Multiple)
|
|
)
|
|
|
|
this.SetBlockGeom()
|
|
this.UpdateBounds()
|
|
this.SetLodLevelsCount()
|
|
|
|
-- Get list of props that'll be used to generate this node's lod-object:
|
|
local srcObjs = (getObjs lodFilter:genLodLevel info:true)
|
|
sourceHash = getSourceHash getFresh:true srcObjItems:srcObjs
|
|
|
|
-- Get total lodObjCount for this group:
|
|
lodObjCount = if (sourceHash == 0) then 0 else 1
|
|
for grp in _groupList do
|
|
(
|
|
lodObjCount += grp.lodObjCount
|
|
)
|
|
|
|
-- Check for missing lod-object:
|
|
local propCount = srcObjs.count
|
|
local needsLod = (dummySlod) or (propCount != 0)
|
|
hasLod = (lodObjItem != undefined) and (isValidNode lodObjItem.node)
|
|
|
|
-- Does this node use tree-props?
|
|
hasTrees = False
|
|
for objItem in srcObjs while (not hasTrees) do
|
|
(
|
|
hasTrees = objItem.isTree
|
|
)
|
|
|
|
-- Set as unsynced if lod-object is missing, or no longer requires one:
|
|
lodSynced = if needsLod then
|
|
(
|
|
(hasLod and (sourceHash == lodObjHash))
|
|
)
|
|
else
|
|
(
|
|
if hasLod then False else undefined
|
|
)
|
|
|
|
if (genLodLevel == 1) then
|
|
(
|
|
-- Should this lod-node be water-reflected?
|
|
dontWaterReflect = True
|
|
for objItem in srcObjs while (dontWaterReflect) do
|
|
(
|
|
dontWaterReflect = (not objItem.genWaterReflect)
|
|
)
|
|
|
|
-- First-level lods just get lodsSynced value from themselves:
|
|
lodsSynced = lodSynced
|
|
|
|
-- Get blockByPropName from first object in block:
|
|
if (blockByPropName == undefined) do
|
|
(
|
|
setBlockPropName()
|
|
)
|
|
)
|
|
else
|
|
(
|
|
-- Get child-node LOD-sync status:
|
|
lodsSynced = undefined
|
|
for item in _groupList where (item.lodsSynced != undefined) while (lodsSynced != false) do
|
|
(
|
|
lodsSynced = item.lodsSynced
|
|
)
|
|
for item in _groupList where (item.lodSynced != undefined) while (lodsSynced != false) do
|
|
(
|
|
lodsSynced = item.lodSynced
|
|
)
|
|
)
|
|
|
|
if doPrompt do
|
|
PopPrompt()
|
|
|
|
return lodSynced
|
|
),
|
|
|
|
-- Returns true if LOD-group should be split up into siblings - if it has too many objects, or is too big:
|
|
fn ShouldSplitGroup =
|
|
(
|
|
-- Split groups if they will be lod-parents to >maxLodCount objects:
|
|
local subLodObjCount = 0
|
|
for itemList in #(_objItems, _groupList) do
|
|
(
|
|
for item in itemList where (item.sourceHash != 0) do (subLodObjCount += 1)
|
|
)
|
|
local shouldSplit = (subLodObjCount > gRsLodCombinerVals.maxLodCount)
|
|
|
|
return shouldSplit
|
|
),
|
|
|
|
-- Generate smaller sibling LOD-block groups, based on current one
|
|
fn SplitGroup allowEmpty:False =
|
|
(
|
|
local sourceGrp = This
|
|
local newBlocks = #()
|
|
|
|
local halfRadius = 0.5 * blockRadius
|
|
|
|
-- List all objItems in group/subgroups:
|
|
local allObjItems = this.GetObjs recurse:True info:True
|
|
local objsAdded = #{}
|
|
objsAdded.count = allObjItems.count
|
|
|
|
case blockType of
|
|
(
|
|
#none: -- Split non-gridded blocks in the middle of longest axis
|
|
(
|
|
local longAxis = if (boundsSize.Y > boundsSize.X) then 2 else 1
|
|
local midVal = blockPos[longAxis]
|
|
|
|
local objItemLists = #(#(),#())
|
|
|
|
-- Split object-list in two along longest axis:
|
|
for item in allObjItems do
|
|
(
|
|
if (item.node.pos[longAxis] > midVal) then
|
|
Append objItemLists[1] item
|
|
else
|
|
Append objItemLists[2] item
|
|
)
|
|
|
|
-- Set all objsAdded flags to true:
|
|
objsAdded = -objsAdded
|
|
|
|
for objList in objItemLists do
|
|
(
|
|
local newGrp = this.Clone()
|
|
newGrp.SetObjItems objList
|
|
newGrp.UpdateBounds()
|
|
Append newBlocks newGrp
|
|
)
|
|
)
|
|
#box:
|
|
(
|
|
-- Split boxes to smaller boxes:
|
|
local offsets = #([1,1,0], [1,-1,0], [-1,-1,0], [-1,1,0])
|
|
local offsets = for pos in offsets collect (pos * halfRadius)
|
|
|
|
for offset in offsets do
|
|
(
|
|
local newGrp = this.Clone()
|
|
newGrp.blockPos = (blockPos + offset)
|
|
newGrp.blockRadius = halfRadius
|
|
Append newBlocks newGrp
|
|
)
|
|
)
|
|
#hex: -- Split hexes to tris:
|
|
(
|
|
if (hexQuadTriHeight == undefined) do
|
|
this.SetBlockGeom()
|
|
|
|
local outerVertOffsets = #([-halfRadius, hexQuadTriHeight, 0], [halfRadius, hexQuadTriHeight, 0], [blockRadius, 0, 0], [halfRadius, -hexQuadTriHeight, 0], [-halfRadius, -hexQuadTriHeight, 0], [-blockRadius, 0, 0])
|
|
local outerVerts = for offset in outerVertOffsets collect (blockPos + offset)
|
|
append outerVerts outerVerts[1]
|
|
|
|
-- Create six triangle-blocks from this hex-block
|
|
local triFlipped = false
|
|
for n = 1 to 6 do
|
|
(
|
|
local triVerts = #(blockPos, outerVerts[n], outerVerts[n + 1])
|
|
local triMidPos = (triVerts[1] + triVerts[2] + triVerts[3]) / 3
|
|
|
|
local thisRot = if (blockRot == undefined) then 0 else (copy blockRot)
|
|
local triRot = if triFlipped then (thisRot + 180) else (thisRot)
|
|
local triRadius = distance blockPos triMidPos
|
|
|
|
-- Create triangles around hex's pivot:
|
|
local triBlock = this.Clone()
|
|
triBlock.blockType = #Tri
|
|
triBlock.childBlockType = #Tri
|
|
triBlock.blockPos = triMidPos
|
|
triBlock.blockRot = triRot
|
|
triBlock.blockRadius = triRadius
|
|
triBlock.setBlockGeom()
|
|
triFlipped = (not triFlipped)
|
|
Append newBlocks triBlock
|
|
)
|
|
)
|
|
#tri: -- Split tris to smaller tris:
|
|
(
|
|
-- Make sure verts don't have extra scaling on them:
|
|
setBlockGeom scaleUp:false
|
|
|
|
local outerPoints = #(triVerts[1], triVerts[2], triVerts[3], triVerts[1])
|
|
local midPoints = for n = 1 to 3 collect
|
|
(
|
|
0.5 * (outerPoints[n] + outerPoints[n + 1])
|
|
)
|
|
insertItem midPoints[3] midPoints 1
|
|
|
|
local triVertLists = for n = 1 to 3 collect #(outerPoints[n], midPoints[n + 1], midPoints[n])
|
|
append triVertLists #(midPoints[1], midPoints[2], midPoints[3])
|
|
|
|
for triNum = 1 to 4 do
|
|
(
|
|
local triVerts = triVertLists[triNum]
|
|
local triMidPos = (triVerts[1] + triVerts[2] + triVerts[3]) / 3
|
|
|
|
local thisRot = if (blockRot == undefined) then 0 else (copy blockRot)
|
|
local triRot = if (triNum == 4) then (thisRot + 180) else (thisRot)
|
|
local triRadius = distance blockPos triMidPos
|
|
|
|
local triBlock = this.Clone()
|
|
triBlock.blockPos = triMidPos
|
|
triBlock.blockRot = triRot
|
|
triBlock.blockRadius = triRadius
|
|
|
|
triBlock.setBlockGeom()
|
|
|
|
Append newBlocks triBlock
|
|
)
|
|
)
|
|
)
|
|
|
|
if (blockType != #none) do
|
|
(
|
|
for blockItem in newBlocks while (objsAdded.numberSet != allObjItems.count) do
|
|
(
|
|
local newBlockObjs = #()
|
|
|
|
for objNum = (-objsAdded) do
|
|
(
|
|
local objItem = allObjItems[objNum]
|
|
|
|
if (blockItem.IsInBlockArea objItem.node) do
|
|
(
|
|
Append newBlockObjs objItem
|
|
objsAdded[objNum] = True
|
|
)
|
|
)
|
|
|
|
blockItem.SetObjItems newBlockObjs
|
|
)
|
|
)
|
|
|
|
-- Empty current block's object-list:
|
|
-- (For safety, this keeps objects that weren't inside the new blocks for some reason:
|
|
local remainingObjs = for objNum in (-objsAdded) collect allObjItems[objNum]
|
|
this.SetObjItems remainingObjs
|
|
if (remainingObjs.count != 0) do
|
|
Append newBlocks this
|
|
|
|
-- Filter out empty blocks:
|
|
if not allowEmpty do
|
|
newBlocks = for newBlock in newBlocks where ((newBlock.GetObjItems()).count != 0) collect newBlock
|
|
|
|
return newBlocks
|
|
),
|
|
|
|
-- Renames a set of SLOD-groups, adding number-suffixes to avoid repeats for a particular LOD-level:
|
|
fn setGrpNames renameGrps newName recurse:false =
|
|
(
|
|
if (renameGrps.count == 0) do
|
|
return False
|
|
|
|
local changed = false
|
|
|
|
local grps = for item in renameGrps collect item
|
|
|
|
if recurse do
|
|
(
|
|
-- Add groups' subgroups to rename-list:
|
|
local grpNum = 0
|
|
while (grpNum < grps.count) do
|
|
(
|
|
grpNum += 1
|
|
|
|
-- Don't add level-1 groups to rename-list, as they don't get their own groupNames:
|
|
if (grps[grpNum].genLodLevel < 2) do
|
|
(
|
|
join grps (grps[grpNum].GetGroupList())
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Filter out LOD-level groups:
|
|
grps = for grp in grps where (grp.genLodLevel > 1) collect grp
|
|
|
|
-- Seperate selected groups per map:
|
|
local mapNodes = #()
|
|
local perMapGrps = #()
|
|
|
|
-- Make array of selected slod-groups per lod-level per map:
|
|
for grpData in grps do
|
|
(
|
|
local mapNode = grpData.GetRootData()
|
|
local mapNum = findItem mapNodes mapNode
|
|
|
|
local grpLevel = grpData.genLodLevel
|
|
|
|
if (mapNum == 0) do
|
|
(
|
|
append mapNodes mapNode
|
|
append perMapGrps #()
|
|
mapNum = mapNodes.count
|
|
)
|
|
|
|
if (perMapGrps[mapNum][grpLevel] == undefined) do
|
|
(
|
|
perMapGrps[mapNum][grpLevel] = #()
|
|
)
|
|
|
|
append perMapGrps[mapNum][grpLevel] grpData
|
|
)
|
|
|
|
-- Filter out undefined items from lists:
|
|
perMapGrps = for lodLevelGrps in perMapGrps collect (for item in lodLevelGrps where (item != undefined) collect item)
|
|
|
|
for mapNum = 1 to mapNodes.count do
|
|
(
|
|
-- Collect nodes for map that are not due to be renamed
|
|
local mapGrps = (mapNodes[mapNum].GetSubGroups recurse:true)
|
|
mapGrps = for grp in mapGrps where (grp.genLodLevel > 1) and (grp.groupName != Undefined) collect grp
|
|
mapGrps = for grp in mapGrps where (FindItem grps grp == 0) collect grp
|
|
|
|
-- We don't want to generate objects with names more than 30 characters long - clamp name-length:
|
|
local thisMapName = mapNodes[mapNum].mapName
|
|
local maxLength = (30 - thisMapName.count - 8)
|
|
|
|
local setNewName = newName
|
|
if (setNewName.count > maxLength) do
|
|
(
|
|
setNewName = RsStripSuffixNums (subString newName 1 maxLength)
|
|
)
|
|
|
|
-- Find groups in same container with same name and lod-level:
|
|
for lodLevelGrps in perMapGrps[mapNum] do
|
|
(
|
|
local lodLevel = lodLevelGrps[1].genLodLevel
|
|
mapLodLevelGrps = for grp in mapGrps where (grp.genLodLevel == lodLevel) collect grp
|
|
|
|
local exactMatchFound = False
|
|
local matchedNameNums = #()
|
|
|
|
local newNameNoNum = (RsStripSuffixNums setNewName)
|
|
local newNameHasNum = (newNameNoNum != setNewName)
|
|
local matchNamePattern = newNameNoNum + "*"
|
|
|
|
-- Find existing matching group-names, or same name but with number-suffix
|
|
for grp in mapLodLevelGrps where (FindItem lodLevelGrps item == 0) do
|
|
(
|
|
local grpName = grp.groupName
|
|
|
|
if (MatchPattern grpName pattern:matchNamePattern) do
|
|
(
|
|
if (not exactMatchFound) and (MatchPattern grpName pattern:setNewName) do
|
|
exactMatchFound = True
|
|
|
|
-- Collect suffix-number value
|
|
local grpNameNoNum = RsStripSuffixNums grpName
|
|
if (grpNameNoNum.count != grpName.count) do
|
|
(
|
|
-- Get number-string from group-name
|
|
local grpNumStr = (Substring grpName (newNameNoNum.count + 1) -1)
|
|
|
|
-- If this substring definately only contains numbers
|
|
if (RsStripSuffixNums grpNumStr).count == 0 do
|
|
(
|
|
local grpNameNum = (grpNumStr as Integer)
|
|
AppendIfUnique matchedNameNums grpNameNum
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Strip index from matched name, so new unused numbers can be used instead
|
|
if exactMatchFound do
|
|
setNewName = newNameNoNum
|
|
|
|
-- Get the next available suffix-index
|
|
local suffixNum = 1
|
|
if (matchedNameNums.count != 0) do
|
|
(
|
|
Sort matchedNameNums
|
|
suffixNum = matchedNameNums[matchedNameNums.count]
|
|
suffixNum += 1
|
|
)
|
|
|
|
local addSuffix = exactMatchFound or (lodLevelGrps.count != 1)
|
|
for grpData in lodLevelGrps do
|
|
(
|
|
local grpName = if addSuffix then
|
|
(
|
|
local numString = (FormattedPrint suffixNum format:"02i")
|
|
suffixNum += 1
|
|
local thisNewName = subString newNameNoNum 1 (maxLength - numString.count)
|
|
|
|
(thisNewName + numString)
|
|
)
|
|
else setNewName
|
|
|
|
--format "Setting groupName to % %\n" grpName grpData.guid
|
|
grpData.groupName = grpName
|
|
|
|
local objGrps = #(grpData)
|
|
if (grpData.genLodLevel == 2) do
|
|
(
|
|
join objGrps (grpData.GetGroupList())
|
|
)
|
|
|
|
for thisGrp in objGrps do
|
|
(
|
|
thisGrp.showName()
|
|
|
|
local lodObj = (thisGrp.getLodObjs recurse:False)[1]
|
|
|
|
if (lodObj != undefined) do
|
|
(
|
|
lodObj.name = thisGrp.lodObjName
|
|
)
|
|
)
|
|
|
|
changed = true
|
|
)
|
|
)
|
|
)
|
|
|
|
return changed
|
|
),
|
|
|
|
-- Save group-data to xml element:
|
|
fn store xmlWriter objsList:#() defaultStruct: =
|
|
(
|
|
xmlWriter.WriteStartElement "group"
|
|
|
|
-- Use this struct-instance to determine default values:
|
|
if (defaultStruct == unsupplied) do
|
|
(
|
|
defaultStruct = RsLodCombinerGroup()
|
|
)
|
|
|
|
-- Write attributes:
|
|
-- Write for all lod-levels
|
|
local attribsList =
|
|
#(
|
|
dataPair name:"guid" val:guid,
|
|
dataPair name:"genLodLevel" val:genLodLevel,
|
|
dataPair name:"blockPos" val:blockPos,
|
|
dataPair name:"blockRot" val:blockRot,
|
|
dataPair name:"worldNormal" val:worldNormal,
|
|
dataPair name:"blockOrigin" val:blockOrigin
|
|
)
|
|
|
|
if (genLodLevel == 1) then
|
|
(
|
|
-- Only store for lowest-level LODs:
|
|
join attribsList \
|
|
#(
|
|
dataPair name:"isResidentBlock" val:isResidentBlock,
|
|
dataPair name:"lodDistIdx" val:lodDistIdx
|
|
)
|
|
)
|
|
else
|
|
(
|
|
-- Only store these values for non-lowest-level LODs:
|
|
join attribsList \
|
|
#(
|
|
dataPair name:"groupName" val:groupName,
|
|
dataPair name:"childBlockType" val:childBlockType,
|
|
dataPair name:"childBlockPos" val:childBlockPos,
|
|
dataPair name:"childBlockRadius" val:childBlockRadius,
|
|
dataPair name:"blockByProp" val:blockByProp
|
|
)
|
|
)
|
|
|
|
for item in attribsList where
|
|
(
|
|
-- Either don't store default values, or don't store undefined values:
|
|
if (item.name == "worldNormal") then (item.val != (getProperty defaultStruct item.name)) else (item.val != undefined)
|
|
) do
|
|
(
|
|
local writeVal = if (isKindOf item.val booleanClass) then (if item.val then "1" else "0") else (item.val as string)
|
|
xmlWriter.WriteAttributeString item.name writeVal
|
|
)
|
|
|
|
-- Store objects-list:
|
|
for item in _objItems do
|
|
(
|
|
item.store xmlWriter objsList:objsList
|
|
)
|
|
|
|
-- Store lodObjs data, if defined:
|
|
if (lodObjItem != undefined) do
|
|
(
|
|
lodObjItem.store xmlWriter objsList:objsList
|
|
)
|
|
|
|
-- Store linked containerLod parent:
|
|
if (containerLodParent != undefined) do
|
|
(
|
|
containerLodParent.store xmlWriter objsList:objsList
|
|
)
|
|
|
|
-- Add subgroup data:
|
|
for item in _groupList do
|
|
item.Store xmlWriter objsList:objsList defaultStruct:defaultStruct
|
|
|
|
xmlWriter.WriteEndElement()
|
|
|
|
return OK
|
|
),
|
|
|
|
-- Set up group-struct based on xml element:
|
|
fn Restore xmlReader parentData: objsList:#() setBlockType:#None setBlockRadius:-1 readVersion:0 &progressVal: progressMax: &cancelled: &doReblock: =
|
|
(
|
|
if (progressMax != 0) do
|
|
(
|
|
progressVal += 1
|
|
|
|
if not (progressUpdate (100.0 * progressVal / progressMax)) do
|
|
(
|
|
cancelled = True
|
|
return false
|
|
)
|
|
)
|
|
|
|
local restoredGrp = RsLodCombinerGroup blockType:setBlockType blockRadius:setBlockRadius
|
|
restoredGrp.SetParentData parentData
|
|
|
|
for item in
|
|
#(
|
|
dataPair name:"guid" class:string,
|
|
dataPair name:"groupName" class:string,
|
|
dataPair name:"isResidentBlock" class:booleanClass,
|
|
dataPair name:"lodDistIdx" class:integer,
|
|
|
|
dataPair name:"blockPos" class:undefined,
|
|
dataPair name:"blockRot" class:float,
|
|
dataPair name:"blockOrigin" class:undefined,
|
|
|
|
dataPair name:"childBlockType" class:name,
|
|
dataPair name:"childBlockPos" class:undefined,
|
|
dataPair name:"childBlockRadius" class:float,
|
|
|
|
dataPair name:"blockByProp" class:booleanClass,
|
|
dataPair name:"genLodLevel" class:integer
|
|
)
|
|
do
|
|
(
|
|
local getVal = xmlReader.Item[item.name]
|
|
|
|
if (getVal != undefined) do
|
|
(
|
|
getVal = case item.class of
|
|
(
|
|
undefined:(execute getVal)
|
|
booleanClass:(getVal == "1")
|
|
default:(getVal as (item.class))
|
|
)
|
|
|
|
setProperty restoredGrp item.name getVal
|
|
)
|
|
)
|
|
|
|
-- Update values from older data:
|
|
if (readVersion < 1) do
|
|
(
|
|
-- Work out genLodLevel value for older data:
|
|
case of
|
|
(
|
|
-- Old block-structs are converted to level-1 groups:
|
|
(xmlReader.name == "block"):(restoredGrp.genLodLevel = 1)
|
|
-- Top-level groups are given the maximum lod-level:
|
|
(parentData.genLodLevel == 0):(restoredGrp.genLodLevel = gRsLodCombinerVals.lodSuffixes.count)
|
|
-- All other groups work out their lod-level from their parent-group:
|
|
Default:(restoredGrp.genLodLevel = (parentData.genLodLevel - 1))
|
|
)
|
|
|
|
-- In earlier data, only level-2 slodgroups have child-gridding:
|
|
case of
|
|
(
|
|
(restoredGrp.genLodLevel == 2):
|
|
(
|
|
local getVal = xmlReader.Item["blockType"]
|
|
if (getVal != undefined) do
|
|
(
|
|
restoredGrp.childBlockType = (getVal as name)
|
|
)
|
|
local getVal = xmlReader.Item["blockRadius"]
|
|
if (getVal != undefined) do
|
|
(
|
|
restoredGrp.childBlockRadius = (getVal as float)
|
|
)
|
|
local getVal = xmlReader.Item["groupPos"]
|
|
if (getVal != undefined) do
|
|
(
|
|
restoredGrp.childBlockPos = (execute getVal)
|
|
)
|
|
)
|
|
(restoredGrp.genLodLevel > 2):
|
|
(
|
|
restoredGrp.childBlockType = #none
|
|
restoredGrp.blockType = #none
|
|
)
|
|
)
|
|
)
|
|
|
|
-- LOD-block groups aren't allowed their own names, and take their blockByProp value from their parents:
|
|
if (restoredGrp.genLodLevel < 2) do
|
|
(
|
|
restoredGrp.groupName = undefined
|
|
restoredGrp.blockByProp = parentData.blockByProp
|
|
)
|
|
|
|
-- Reconstitute group's sub-item lists from xml, if it has any sub-elements:
|
|
if not xmlReader.IsEmptyElement do
|
|
(
|
|
xmlReader.Read()
|
|
|
|
local stopLoop = cancelled or xmlReader.IsEmptyElement
|
|
local blankLoopNum = 0
|
|
|
|
while (not stopLoop) do
|
|
(
|
|
local nodeType = xmlReader.NodeType
|
|
stopLoop = (nodeType == nodeType.EndElement) or (nodeType == nodeType.None) or (blankLoopNum == 2)
|
|
|
|
if (not stopLoop) do
|
|
(
|
|
local elemName = xmlReader.name
|
|
|
|
case elemName of
|
|
(
|
|
-- Prop/LOD objects:
|
|
"object":
|
|
(
|
|
blankLoopNum = 0
|
|
|
|
local newObjItem = RsLodCombinerObject.Restore xmlReader objsList:objsList parentData:restoredGrp readVersion:readVersion
|
|
|
|
case of
|
|
(
|
|
(newObjItem == dontCollect):()
|
|
(newObjItem.lodLevel == 0):(Append (restoredGrp.GetObjItems()) newObjItem)
|
|
(newObjItem.lodLevel == -1):(restoredGrp.containerLodParent = newObjItem)
|
|
(newObjItem.lodLevel > 0):(restoredGrp.lodObjItem = newObjItem)
|
|
)
|
|
)
|
|
-- Blank element...
|
|
"":
|
|
(
|
|
blankLoopNum += 1
|
|
)
|
|
-- SLOD/LOD-groups:
|
|
Default:
|
|
(
|
|
blankLoopNum = 0
|
|
|
|
local newGroup = RsLodCombinerGroup.restore xmlReader objsList:objsList parentData:restoredGrp setBlockType:restoredGrp.childBlockType setBlockRadius:restoredGrp.childBlockRadius readVersion:readVersion progressVal:&progressVal progressMax:progressMax cancelled:&cancelled doReblock:&doReblock
|
|
|
|
if (newGroup != dontCollect) do
|
|
(
|
|
local GrpList = restoredGrp.GetGroupList()
|
|
append GrpList newGroup
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Read next element:
|
|
xmlReader.Read()
|
|
)
|
|
|
|
stopLoop = (stopLoop or cancelled)
|
|
)
|
|
)
|
|
|
|
-- Set "isResidentProp" value on props if this is a Memory Resident block:
|
|
if restoredGrp.isResidentBlock do
|
|
(
|
|
(restoredGrp.GetObjItems()).isResidentProp = True
|
|
)
|
|
|
|
-- See if any child-groups have the same saved LodLevel.
|
|
-- If so, the reader's gone wrong, or data has been saved after using previous wonky reader:
|
|
-- Fixed by moving dodgy nodes up until they hit the right level.
|
|
local GrpList = restoredGrp.GetGroupList()
|
|
for n = GrpList.Count to 1 by -1 do
|
|
(
|
|
local SubGrp = GrpList[n]
|
|
|
|
if (subGrp.genLodLevel == restoredGrp.genLodLevel) then
|
|
(
|
|
format " Arse! Xml-tree is out of whack. Fixing by moving node up a level: \"%\" (level %)\n" subGrp.groupName newGroup.genLodLevel
|
|
append ((restoredGrp.GetParentData()).GetGroupList()) subGrp
|
|
DeleteItem GrpList n
|
|
)
|
|
)
|
|
|
|
-- Retrieve lod-object's hash, so we can tell if this node is up-to-date:
|
|
if (restoredGrp.lodObjItem != undefined) do
|
|
(
|
|
restoredGrp.lodObjHash = restoredGrp.lodObjItem.sourceHash
|
|
)
|
|
|
|
-- Fix any unnamed subgroups:
|
|
if (restoredGrp.genLodLevel > 2) and (restoredGrp.groupName != undefined) do
|
|
(
|
|
local unnamedGrps = for grp in (restoredGrp.GetGroupList()) where (grp.groupName == undefined) collect grp
|
|
if (unnamedGrps.count != 0) do
|
|
restoredGrp.SetGrpNames unnamedGrps restoredGrp.groupName
|
|
unnamedGrps = undefined
|
|
)
|
|
|
|
-- We no longer want to allow non-gridded lod-groups, so convert them to the default Hex:
|
|
local isUngriddedBlockParent = False
|
|
if (restoredGrp.childBlockType == #None) do
|
|
(
|
|
restoredGrp.childBlockType = #Box
|
|
isUngriddedBlockParent = True
|
|
|
|
-- Trigger reblock (if this block's parent isn't going to be reblocked)
|
|
local parData = restoredGrp.GetParentData()
|
|
doReblock = not ((isKindOf parData RsLodCombinerGroup) and (parData.childBlockType == #None))
|
|
if doReblock do
|
|
format "SETTING \"NONE\" SLOD-GROUP TO \"HEX\": %\n" restoredGrp.groupName
|
|
)
|
|
|
|
-- If something in this group's hierarchy triggered a reblock:
|
|
if doReblock and (isUngriddedBlockParent or (restoredGrp.blockType == #None) or (restoredGrp.genLodLevel == gRsLodCombinerVals.lodSuffixes.count)) do
|
|
(
|
|
format "* Reblocking parent of fixed-up group: %\n" restoredGrp.groupName
|
|
|
|
doReblock = False
|
|
restoredGrp.ReassignBlocks()
|
|
)
|
|
|
|
return restoredGrp
|
|
),
|
|
|
|
-- Text to print in treeView for group struct:
|
|
fn ShowName =
|
|
(
|
|
if (genLodLevel == 1) then
|
|
(
|
|
if (_parentData.lodPrefixName == undefined) do
|
|
_parentData.ShowName()
|
|
|
|
-- LOD-block objects take most of their name from their parents:
|
|
this.lodPrefixName = _parentData.lodPrefixName + (formattedPrint this.childNum format:"02i") + "_"
|
|
)
|
|
else
|
|
(
|
|
local topNode = this.GetRootData()
|
|
this.lodPrefixName = topNode.mapName + "_" + (this.groupName as string) + "_"
|
|
)
|
|
|
|
local lodSuffix = if this.dummySlod then (gRsLodCombinerVals.dummySlodSuffix) else (gRsLodCombinerVals.lodSuffixes[this.genLodLevel])
|
|
|
|
this.lodObjName = this.lodPrefixName + lodSuffix
|
|
|
|
return lodObjName
|
|
),
|
|
|
|
-- Generate new sub-groups to hold objects:
|
|
fn CreateBlocks objs =
|
|
(
|
|
if (objs.count == 0) do
|
|
return #()
|
|
|
|
local newGrps = #()
|
|
|
|
local newLodLevel = (genLodLevel - 1)
|
|
local isLodBlocks = (newLodLevel == 1)
|
|
local doBlockByProp = (isLodBlocks and blockByProp)
|
|
|
|
-- Get implicitly-set values from parent, if undefined:
|
|
getParentVals()
|
|
|
|
local objNames = #()
|
|
local objListNums = #()
|
|
local objBlockPosList = #()
|
|
|
|
-- See if any of these objects' models are marked as Memory Resident:
|
|
local residentObjNums = #{}
|
|
if isLodBlocks do
|
|
(
|
|
residentObjNums.count = objs.count
|
|
getResidentRefNums()
|
|
for objNum = 1 to objs.count do
|
|
(
|
|
local objRef = objs[objNum].refDef
|
|
residentObjNums[objNum] = ((objRef != undefined) and residentRefNums[objRef.num])
|
|
)
|
|
)
|
|
local hasResidents = (residentObjNums.numberSet != 0)
|
|
|
|
local groupList = this.GetGroupList()
|
|
|
|
-- Split object-list per group if blocking per prop-name:
|
|
if doBlockByProp or hasResidents then
|
|
(
|
|
for objNum = 1 to objs.count do
|
|
(
|
|
local obj = objs[objNum]
|
|
local isResident = residentObjNums[objNum]
|
|
|
|
local objName = case of
|
|
(
|
|
isResident:(getPropLodDistIdx obj) -- Ranges from 0 to residentDistLimits.count
|
|
doBlockByProp:(toLower obj.objectName)
|
|
Default:(-1) -- Used for non-residents, when not blocking by prop-name
|
|
)
|
|
|
|
local findNum = findItem objNames objName
|
|
if (findNum == 0) do
|
|
(
|
|
local newArray = #{}
|
|
--newArray.count = objs.count
|
|
|
|
append objListNums newArray
|
|
append objNames objName
|
|
findNum = objNames.count
|
|
|
|
-- Get initial-blockPos from first subgroup with matching blockByPropName:
|
|
local nameBlockPos = undefined
|
|
for grp in groupList while (nameBlockPos == undefined) do
|
|
(
|
|
if (grp.blockByPropName == objName) do
|
|
(
|
|
if (grp.blockOrigin == undefined) do
|
|
(
|
|
grp.blockOrigin = grp.blockPos
|
|
)
|
|
|
|
nameBlockPos = grp.blockOrigin
|
|
)
|
|
)
|
|
append objBlockPosList nameBlockPos
|
|
)
|
|
|
|
objListNums[findNum][objNum] = True
|
|
)
|
|
)
|
|
else
|
|
(
|
|
Append objListNums #{1..objs.count}
|
|
)
|
|
|
|
for listNum = 1 to objListNums.count do
|
|
(
|
|
local nameVal = objNames[listNum]
|
|
local setResidentBlock = hasResidents and (isKindOf nameVal number) and (nameVal >= 0)
|
|
|
|
-- The lodDistIdx for Resident blocks will have been saved to objNames list:
|
|
local lodDistIdx = 0
|
|
if setResidentBlock do
|
|
lodDistIdx = objNames[listNum]
|
|
|
|
local theseNewGrps = #()
|
|
local objList = for objNum in objListNums[listNum] collect objs[objNum]
|
|
|
|
-- Create new blocks:
|
|
(
|
|
pushPrompt "Generating block-grid"
|
|
|
|
local blockGridOrigin = if doBlockByProp then objBlockPosList[listNum] else childBlockPos
|
|
|
|
-- Find box around objects' pivots:
|
|
local objPosList = for obj in objList collect obj.pos
|
|
|
|
local minPos
|
|
local maxPos
|
|
local listBoundsSize
|
|
|
|
if (blockGridOrigin == undefined) then
|
|
(
|
|
RsGetBBox objPosList &minPos &maxPos
|
|
listBoundsSize = maxPos - minPos
|
|
|
|
-- Set blockPos to midpoint of group's bounding-box:
|
|
blockGridOrigin = minPos + (0.5 * listBoundsSize)
|
|
blockGridOrigin.z = minPos.z
|
|
|
|
if (not doBlockByProp) do
|
|
(
|
|
childBlockPos = copy blockGridOrigin
|
|
)
|
|
)
|
|
else
|
|
(
|
|
listBoundsSize = [0,0,0]
|
|
|
|
-- if blockPos is already defined, make bounding-box cover same area but with this pivot:
|
|
for pos in objPosList do
|
|
(
|
|
local objOffset = pos - blockGridOrigin
|
|
|
|
for n = 1 to 3 do
|
|
(
|
|
local val = abs objOffset[n]
|
|
if (val > listBoundsSize[n]) do
|
|
(
|
|
listBoundsSize[n] = val
|
|
)
|
|
)
|
|
)
|
|
|
|
minPos = blockGridOrigin - listBoundsSize
|
|
maxPos = blockGridOrigin + listBoundsSize
|
|
listBoundsSize *= 2
|
|
)
|
|
|
|
-- Shift blocking-centre if generating tris via hexes:
|
|
if (childBlockType == #tri) do
|
|
(
|
|
blockGridOrigin -= childBlockRadius
|
|
minPos.Y -= childBlockRadius
|
|
listBoundsSize.Y += childBlockRadius
|
|
)
|
|
|
|
if (childBlockType == #box) then
|
|
-- Generate box-blocks to cover bounds:
|
|
(
|
|
-- blockRadius is the block's radius...
|
|
local childBlockWidth = 2 * childBlockRadius
|
|
|
|
--box pos:blockGridOrigin width:(maxPos.x - minPos.x) length:(maxPos.y - minPos.y)
|
|
|
|
-- Generate blockRadius grid around pos:
|
|
local xPosList = (for xPos = blockGridOrigin.x to (maxPos.x + childBlockWidth) by childBlockWidth collect xPos)
|
|
join xPosList (for xPos = (blockGridOrigin.x - childBlockWidth) to (minPos.x - childBlockWidth) by -childBlockWidth collect xPos)
|
|
local yPosList = (for yPos = blockGridOrigin.y to (maxPos.y + childBlockWidth) by childBlockWidth collect yPos)
|
|
join yPosList (for yPos = (blockGridOrigin.y - childBlockWidth) to (minPos.y - childBlockWidth) by -childBlockWidth collect yPos)
|
|
|
|
for xPos in xPosList do
|
|
(
|
|
join theseNewGrps \
|
|
(
|
|
for yPos in yPosList collect
|
|
(
|
|
--box pos:[xPos, yPos, blockGridOrigin.z] width:childBlockWidth length:childBlockWidth
|
|
local BoxGrp = RsLodCombinerGroup guid:(genGuid()) lodDistIdx:lodDistIdx genLodLevel:newLodLevel blockRadius:childBlockRadius childBlockType:#box blockPos:[xPos, yPos, blockGridOrigin.z] \
|
|
blockOrigin:blockGridOrigin blockByProp:blockByProp isResidentBlock:setResidentBlock
|
|
BoxGrp.SetParentData This
|
|
BoxGrp
|
|
)
|
|
)
|
|
)
|
|
)
|
|
else
|
|
-- Tris/hexes both start off with hex-blocks..
|
|
(
|
|
-- Double-up radius if hexes are going to be split to tris:
|
|
local hexRadius = if (childBlockType == #tri) then (2 * childBlockRadius) else childBlockRadius
|
|
|
|
local hexHorizSpacing = hexRadius * 1.5
|
|
local hexVerticSpacing = hexRadius * (cos 30.0)
|
|
local hexHeight = 2.0 * hexVerticSpacing
|
|
|
|
-- Get size of hexes in each direction required to cover bounding-box:
|
|
local hexXNum = 1 + (0.5 * listBoundsSize.x / hexHorizSpacing) as integer
|
|
local hexYNum = 1 + (0.5 * listBoundsSize.y / (2 * hexVerticSpacing)) as integer
|
|
|
|
local hexColumn = #()
|
|
for n = -hexYNum to hexYNum do
|
|
(
|
|
local offset = case of
|
|
(
|
|
(n < 0):(-hexVerticSpacing)
|
|
(n > 0):(hexVerticSpacing)
|
|
default:0
|
|
)
|
|
append hexColumn [0, n * hexHeight, 0]
|
|
)
|
|
|
|
local hexStaggerColumn = for hexPos in hexColumn collect (hexPos + [0, hexVerticSpacing,0])
|
|
hexStaggerColumn.count = (hexStaggerColumn.count - 1)
|
|
--append hexStaggerColumn (hexStaggerColumn[1] - [0, hexHeight, 0])
|
|
|
|
local colA, colB
|
|
if (mod hexXNum 2 == 0) then
|
|
(
|
|
colA = hexColumn
|
|
colB = hexStaggerColumn
|
|
)
|
|
else
|
|
(
|
|
colA = hexStaggerColumn
|
|
colB = hexColumn
|
|
)
|
|
|
|
local hexPositions = #()
|
|
|
|
for n = -hexXNum to hexXNum by 2 do
|
|
(
|
|
local thisCol = for pos in colA collect (pos + [n * hexHorizSpacing,0,0])
|
|
join hexPositions thisCol
|
|
)
|
|
for n = (-hexXNum + 1) to hexXNum by 2 do
|
|
(
|
|
local thisCol = for pos in colB collect (pos + [n * hexHorizSpacing,0,0])
|
|
join hexPositions thisCol
|
|
)
|
|
|
|
-- Offset hex-positions to cover group:
|
|
hexPositions = (for pos in hexPositions collect (blockGridOrigin + pos))
|
|
|
|
for hexPos in hexPositions collect
|
|
(
|
|
local hexBlock = RsLodCombinerGroup guid:(genGuid()) lodDistIdx:lodDistIdx genLodLevel:newLodLevel blockType:#hex childBlockType:#hex blockPos:hexPos blockRadius:hexRadius \
|
|
blockOrigin:blockGridOrigin blockByProp:blockByProp isResidentBlock:setResidentBlock
|
|
hexBlock.SetParentData this
|
|
hexBlock.setBlockGeom()
|
|
Append theseNewGrps hexBlock
|
|
)
|
|
)
|
|
|
|
-- Sort blocks by distance from start-point, to ensure blocks closer to centre get filled first:
|
|
(
|
|
local blockDists = for item in theseNewGrps collect (dataPair grp:item dist:(distance blockGridOrigin item.blockPos))
|
|
fn sortBlocks v1 v2 =
|
|
(
|
|
case of
|
|
(
|
|
(v1.dist == v2.dist):0
|
|
(v1.dist > v2.dist):1
|
|
Default:1
|
|
)
|
|
)
|
|
qsort blockDists sortBlocks
|
|
theseNewGrps = for item in blockDists collect item.grp
|
|
)
|
|
|
|
-- Split hex-blocks up into tri-blocks, if group is using that mode:
|
|
if (childBlockType == #Tri) do
|
|
(
|
|
local triBlocks = #()
|
|
|
|
for hexBlock in theseNewGrps do
|
|
Join triBlocks (hexBlock.SplitGroup allowEmpty:True)
|
|
|
|
theseNewGrps = triBlocks
|
|
)
|
|
|
|
popPrompt()
|
|
)
|
|
|
|
-- Add prop-objects to new blocks
|
|
local objsAdded = #{}
|
|
objsAdded.count = objList.count
|
|
for newGrp in theseNewGrps while (objsAdded.numberSet != objsAdded.count) do
|
|
newGrp.AddObjs objList done:&objsAdded firstRecurse:False
|
|
|
|
-- Filter out unoccupied blocks:
|
|
theseNewGrps = for item in theseNewGrps where ((item.GetGroupList()).count != 0) or ((item.GetObjItems()).count != 0) collect item
|
|
|
|
-- Set block z-positions, for showing in viewport:
|
|
(
|
|
local objPosList = for obj in objList collect obj.pos
|
|
local minPos, maxPos
|
|
RsGetBBox objPosList &minPos &maxPos
|
|
local midPosZ = minPos.Z + (0.5 * (maxPos.Z - minPos.Z))
|
|
|
|
for newGrp in theseNewGrps where (isKindOf newGrp.blockPos point3) and ((newGrp.GetObjItems()).count != 0) do
|
|
(
|
|
newGrp.blockPos.Z = midPosZ
|
|
)
|
|
)
|
|
|
|
-- Add new groups to main output-list:
|
|
Join newGrps theseNewGrps
|
|
)
|
|
|
|
return newGrps
|
|
),
|
|
|
|
-- Add objects to LOD-group:
|
|
fn AddObjs addObjList doRedraw:true &done: firstRecurse:true =
|
|
(
|
|
--if firstRecurse do (Format ".AddObjs - %\n" (gRsDebugStack.GetCallerLine()))
|
|
|
|
-- Allow Escape to break if in debug-mode:
|
|
if RsComboLodDebug and keyboard.escpressed do
|
|
return false
|
|
|
|
if (addObjList.count == 0) do
|
|
return False
|
|
|
|
-- Set to True if an Add occurs
|
|
local changed = False
|
|
|
|
-- Get implicitly-set values from parent, if undefined:
|
|
this.GetParentVals()
|
|
|
|
-- Only run these checks for function's first recursion:
|
|
if firstRecurse do
|
|
(
|
|
-- Only allow RsRefs in the same container to be added:
|
|
local contNode = (this.GetRootData()).contNode
|
|
addObjList = for obj in addObjList where (isRsRef obj) and ((Containers.IsInContainer obj) == contNode) collect obj
|
|
|
|
-- Load lod-distances for objects, if they've not already been loaded:
|
|
local refDefs = for obj in addObjList collect obj.refDef
|
|
gRsComboLodFuncs.loadSlodData refDefs
|
|
)
|
|
|
|
-- Set bitarray count to same as object-list, for proper use of inverted array:
|
|
if (done.count == 0) do
|
|
done.count = addObjList.count
|
|
|
|
-- Collect list of objects that fit in current group's area
|
|
local grpAreaObjs = #{}
|
|
grpAreaObjs.count = addObjList.count
|
|
|
|
for objNum = -done do
|
|
(
|
|
local objIsInArea = this.IsInBlockArea addObjList[objNum]
|
|
grpAreaObjs[objNum] = objIsInArea
|
|
)
|
|
|
|
-- This many objects are within this group's area:
|
|
local areaObjsCount = grpAreaObjs.numberSet
|
|
|
|
local groupList = this.GetGroupList()
|
|
|
|
-- Don't bother trying to add to group/subgroups if it doesn't fall within its block-area boundaries:
|
|
if (areaObjsCount != 0) do
|
|
(
|
|
case genLodLevel of
|
|
(
|
|
1: -- Add these objects to LOD-block's objItems list:
|
|
(
|
|
-- Valid objects currently in block's list: (getting list this way to avoid adding duplicates)
|
|
local curObjList = getObjs()
|
|
|
|
for objNum = grpAreaObjs do
|
|
(
|
|
local obj = addObjList[objNum]
|
|
|
|
-- Only actually add to this group's object-list if the object wasn't here already:
|
|
if (appendIfUnique curObjList obj) do
|
|
(
|
|
Append _objItems (RsLodCombinerObject.create obj parentData:This lodDistIdx:lodDistIdx)
|
|
changed = True
|
|
)
|
|
)
|
|
|
|
done += grpAreaObjs
|
|
)
|
|
Default: -- Add objs to subgroups, if they're inside this group's block-area:
|
|
(
|
|
local changedGrps = #{}
|
|
|
|
pushPrompt "Adding objects to existing LOD-groups.."
|
|
|
|
for grpNum = 1 to groupList.count while ((done * grpAreaObjs).numberSet != areaObjsCount) do
|
|
(
|
|
-- Try adding unassigned objects to subgroup:
|
|
local subGrp = groupList[grpNum]
|
|
|
|
-- Next line's recursion will not attempt to add objects that have been done or are outside this block's area:
|
|
local ignoreObjs = (done + (-grpAreaObjs))
|
|
local added = subGrp.AddObjs addObjList done:&ignoreObjs firstRecurse:False
|
|
|
|
-- Extract done-object indexes from updated IgnoreObjs bitarray, and apply changes to Done bitarray:
|
|
done += (ignoreObjs * grpAreaObjs)
|
|
changedGrps[grpNum] = added
|
|
|
|
if added do
|
|
changed = True
|
|
)
|
|
|
|
popPrompt()
|
|
|
|
-- Collect list of objects that fit in current group's area, but not in existing subgroups:
|
|
local newBlockObjNums = (grpAreaObjs - done)
|
|
local newBlockObjs = for objNum = newBlockObjNums collect addObjList[objNum]
|
|
|
|
-- Generate new sub-groups for these objects:
|
|
if (newBlockObjs.count != 0) do
|
|
(
|
|
local grpCount = groupList.count
|
|
Join groupList (this.CreateBlocks newBlockObjs)
|
|
|
|
if (grpCount != groupList.count) do
|
|
changed = True
|
|
|
|
done += grpAreaObjs
|
|
changedGrps += #{(grpCount + 1)..groupList.count}
|
|
)
|
|
|
|
-- Split changed LOD-blocks if necessary:
|
|
if (genLodLevel > 1) do
|
|
(
|
|
local grpCount = groupList.count
|
|
|
|
local delGrps = #{}
|
|
delGrps.count = grpCount
|
|
|
|
-- Split changed subgroups until they won't split no more:
|
|
while (changedGrps.numberSet != 0) do
|
|
(
|
|
local newChanges = #{}
|
|
|
|
-- Loop through unprocessed changed groups:
|
|
for grpNum = changedGrps do
|
|
(
|
|
if groupList[grpNum].shouldSplitGroup() do
|
|
(
|
|
didSplit = True
|
|
delGrps[grpNum] = True
|
|
|
|
local grpCount = groupList.count
|
|
local newGrps = groupList[grpNum].SplitGroup()
|
|
|
|
join groupList newGrps
|
|
|
|
newChanges += #{(grpCount + 1)..groupList.count}
|
|
)
|
|
)
|
|
|
|
-- Set up new groups for next round of processing:
|
|
changedGrps = newChanges
|
|
)
|
|
|
|
-- Remove split groups:
|
|
if (delGrps.numberSet != 0) do
|
|
(
|
|
-- Remove any generated lod-objects:
|
|
for grpNum = delGrps do
|
|
(
|
|
groupList[grpNum].deleteLods()
|
|
)
|
|
|
|
-- Filter deleted groups out of groupList:
|
|
delGrps.count = groupList.count
|
|
groupList = for grpNum = (-delGrps) collect groupList[grpNum]
|
|
_groupList = groupList
|
|
)
|
|
|
|
-- Higher lod-level group-nodes shouldn't have object-lists, so pass those objects down to lower levels...
|
|
if (genLodLevel > 2) do
|
|
(
|
|
for grp in groupList where ((grp.GetObjItems()).count != 0) do
|
|
(
|
|
local subGrpObjs = for item in grp.GetObjItems() collect item.node
|
|
(grp.GetObjItems()).count = 0
|
|
|
|
local subDone = #{}
|
|
grp.AddObjs subGrpObjs done:&subDone doRedraw:False firstRecurse:False
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Rename any new groups:
|
|
if (groupName != undefined) do
|
|
(
|
|
local newGrps = for grp in groupList where (grp.groupName == undefined) collect grp
|
|
if (newGrps.count != 0) do
|
|
this.SetGrpNames newGrps groupName recurse:True
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
-- If any objects couldn't be added to this group or its subgroups, create a new non-gridded new subgroup instead:
|
|
if firstRecurse and (genLodLevel != 1) and (done.numberSet != done.count) do
|
|
(
|
|
local extraObjs = for objNum = -done collect addObjList[objNum]
|
|
|
|
pushPrompt "Adding objects to new LOD-groups..."
|
|
|
|
-- (createGroups sets current group as ungridded)
|
|
this.CreateGroups #(extraObjs) doRemove:False
|
|
|
|
popPrompt()
|
|
|
|
changed = True
|
|
)
|
|
|
|
-- Update group's bounds and redraw if change was made:
|
|
if changed do
|
|
(
|
|
this.UpdateBounds()
|
|
|
|
if firstRecurse and doRedraw do
|
|
CompleteRedraw()
|
|
)
|
|
|
|
return changed
|
|
),
|
|
|
|
-- Set up sub-blocks based on group's settings
|
|
fn ReassignBlocks =
|
|
(
|
|
PushPrompt ("Reassigning group's blocks: " + (groupName as String))
|
|
|
|
-- Reblocking is no longer required for this group
|
|
this.reblockRequired = False
|
|
|
|
-- Collect objects from group's current subgroups
|
|
local propObjs = this.GetObjs()
|
|
|
|
-- Don't reblock ungridded upper-level groups - try reblocking their child-groups:
|
|
(
|
|
-- Delete existing LOD-objects from current subgroups:
|
|
(
|
|
local lodObjs = #()
|
|
for grp in _groupList do
|
|
(
|
|
join lodObjs (grp.getLodObjs recurse:True)
|
|
)
|
|
if (lodObjs.count != 0) do
|
|
(
|
|
undo off
|
|
(
|
|
delete lodObjs
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Remove existing groups:
|
|
_groupList.count = 0
|
|
|
|
-- Assign objects to new lod-blocks with updated block-shape settings
|
|
(
|
|
PushPrompt ("Creating new subgroups for: " + (groupName as string))
|
|
local done = #{}
|
|
this.AddObjs propObjs done:&done
|
|
PopPrompt()
|
|
)
|
|
|
|
-- Rename new group-blocks:
|
|
if (groupName != Undefined) do
|
|
RsLodCombinerGroup.setGrpNames _groupList groupName recurse:True
|
|
)
|
|
|
|
popPrompt()
|
|
),
|
|
|
|
fn GrpIsEmpty =
|
|
(
|
|
(_objItems.count == 0) and (_groupList.count == 0)
|
|
),
|
|
|
|
-- Remove gridded subgroups, and delete their generated ComboLod objects, if they have no objects:
|
|
fn RemoveEmptyGridBlocks delObjs: =
|
|
(
|
|
local firstRecurse = (delObjs == unsupplied)
|
|
if firstRecurse do
|
|
(
|
|
PushPrompt "Finding/removing empty grid-blocks..."
|
|
delObjs = #()
|
|
)
|
|
|
|
-- Recurse through subgroups first:
|
|
for grp in _groupList do
|
|
(
|
|
grp.removeEmptyGridBlocks delObjs:delObjs
|
|
)
|
|
|
|
-- Clear away empty lod-blocks and gridded groups:
|
|
(
|
|
local newGroupList = #()
|
|
for grp in _groupList do
|
|
(
|
|
if (not grp.GrpIsEmpty()) then
|
|
(
|
|
Append newGroupList grp
|
|
)
|
|
else
|
|
(
|
|
local lodObjs = grp.getLodObjs recurse:True
|
|
if (lodObjs.count != 0) do
|
|
Join delObjs lodObjs
|
|
)
|
|
)
|
|
_groupList = newGroupList
|
|
)
|
|
|
|
-- Delete obsolete lodObjs:
|
|
if firstRecurse do
|
|
(
|
|
if (delObjs.count != 0) do
|
|
Delete delObjs
|
|
|
|
PopPrompt()
|
|
)
|
|
|
|
return OK
|
|
),
|
|
|
|
-- Remove objects from group:
|
|
fn RemoveObjs remObjs removed:#{} checkAll:true removeEmpty:true updateBnds:True =
|
|
(
|
|
-- Get list of objects listed on this node:
|
|
local objs = getObjs recurse:false
|
|
|
|
--format "removeObjs, has %: \"%\"\n" objs.count (for item in _objItems collect (substring ((classof item) as string) 1 20))
|
|
|
|
local remCount = removed.numberSet
|
|
|
|
local remGrpObjNums = #{}
|
|
remGrpObjNums.count = _objItems.count
|
|
|
|
for objNum = 1 to remObjs.count where checkAll or (not removed[objNum]) while checkAll or ((removed.numberSet != remObjs.count) and (remGrpObjNums.numberSet != _objItems.count)) do
|
|
(
|
|
local remObj = remObjs[objNum]
|
|
local findNum = findItem objs remObj
|
|
|
|
if (findNum != 0) do
|
|
(
|
|
removed[objNum] = true
|
|
remGrpObjNums[findNum] = true
|
|
)
|
|
)
|
|
|
|
-- Recollect objs list without the remove-numbered items:
|
|
if (remGrpObjNums.numberSet != 0) do
|
|
(
|
|
_objItems = for objNum = -remGrpObjNums collect _objItems[objNum]
|
|
)
|
|
|
|
--format "removeObjs, keeps %: \"%\"\n" _objItems.count (for item in _objItems collect (substring ((classof item) as string) 1 20))
|
|
|
|
-- Now remove objects from subgroups:
|
|
for item in _groupList while checkAll or (removed.numberSet != remObjs.count) do
|
|
item.RemoveObjs remObjs removed:removed checkAll:checkAll removeEmpty:False updateBnds:false
|
|
|
|
-- Get rid of now-empty LOD-block subgroups:
|
|
if removeEmpty do
|
|
this.RemoveEmptyGridBlocks()
|
|
|
|
return OK
|
|
),
|
|
|
|
-- Add new subgroup(s) to group:
|
|
fn CreateGroups objLists doRemove:true grpName: grpParent: =
|
|
(
|
|
--Format ".CreateGroups - %\n" (gRsDebugStack.GetCallerLine())
|
|
|
|
if (not isKindOf objLists[1] Array) do
|
|
(objLists = #(for item in objLists collect item))
|
|
if (grpName == unsupplied) do
|
|
(grpName = groupName)
|
|
|
|
if (grpParent == unsupplied) do
|
|
(grpParent = this)
|
|
|
|
-- Remove objs from all map's groups, if they're already in use:
|
|
if doRemove do
|
|
(
|
|
local objs = #()
|
|
for objList in objLists do (join objs objList)
|
|
|
|
local topData = grpParent.GetRootData()
|
|
topData.removeObjs objs
|
|
)
|
|
|
|
-- Create new groups:
|
|
local newLodLevel = if (grpParent.genLodLevel == 0) then (gRsLodCombinerVals.lodSuffixes.count) else (grpParent.genLodLevel - 1)
|
|
|
|
-- Split object-lists up if they include multiple IPL Group values - LODs can't include multiple IPLs
|
|
local newObjLists = #()
|
|
for objList in objLists do
|
|
(
|
|
local objsPerIpl = #()
|
|
local iplGroups = #()
|
|
for obj in objList where (IsRsRef obj) do
|
|
(
|
|
local objIpl = GetAttr obj idxIPLGroup
|
|
local idx = FindItem iplGroups objIpl
|
|
if (idx == 0) then
|
|
(
|
|
Append objsPerIpl #(obj)
|
|
Append iplGroups objIpl
|
|
)
|
|
else
|
|
(
|
|
Append objsPerIpl[idx] obj
|
|
)
|
|
)
|
|
Join newObjLists objsPerIpl
|
|
)
|
|
objLists = newObjLists
|
|
|
|
-- Create new ungridded groups, one for each object-list:
|
|
local newGrps = #()
|
|
for grpNum = 1 to objLists.count do
|
|
(
|
|
local newGrp = RsLodCombinerGroup guid:(genGuid()) genLodLevel:newLodLevel blockType:#none blockByProp:blockByProp
|
|
NewGrp.SetParentData GrpParent
|
|
append (grpParent.GetGroupList()) newGrp
|
|
append newGrps newGrp
|
|
|
|
local done = #{}
|
|
newGrp.AddObjs objLists[grpNum] done:&done doRedraw:False
|
|
)
|
|
|
|
-- Rename the new group(s), and subgroups:
|
|
local grpNum = 0
|
|
while (grpNum < newGrps.count) do
|
|
(
|
|
grpNum += 1
|
|
join newGrps (newGrps[grpNum].GetGroupList())
|
|
)
|
|
|
|
RsLodCombinerGroup.SetGrpNames newGrps grpName recurse:True
|
|
|
|
return newGrps
|
|
),
|
|
|
|
-- Adds extra group-nodes to a group if its nodes have anappropriate genLodLevel values for that level:
|
|
fn treeLevelCorrector lodLevel grp: firstRecurse:true =
|
|
(
|
|
if (grp == unsupplied) do (grp = This)
|
|
|
|
local newGrp
|
|
local newGrpList = #()
|
|
|
|
for subGrp in (grp.GetGroupList()) do
|
|
(
|
|
if (subGrp.genLodLevel >= lodLevel) then
|
|
(
|
|
append newGrpList subGrp
|
|
)
|
|
else
|
|
(
|
|
-- Add new subgroup to be this subgroup's new parent:
|
|
if (newGrp == undefined) do
|
|
(
|
|
newGrp = RsLodCombinerGroup guid:(genGuid()) genLodLevel:lodLevel blockByProp:blockByProp
|
|
NewGrp.SetParentData Grp
|
|
|
|
this.SetGrpNames #(newGrp) grp.groupName
|
|
|
|
if (isKindOf grp RsLodCombinerGroup) do
|
|
(
|
|
newGrp.blockType = grp.blockType
|
|
newGrp.blockRadius = grp.blockRadius
|
|
newGrp.blockByProp = grp.blockByProp
|
|
)
|
|
|
|
append newGrpList newGrp
|
|
)
|
|
|
|
append (newGrp.GetGroupList()) subGrp
|
|
subGrp.SetParentData newGrp
|
|
)
|
|
)
|
|
|
|
-- Replace group's grouplist:
|
|
local GrpList = grp.GetGroupList()
|
|
GrpList.Count = 0
|
|
join GrpList newGrpList
|
|
|
|
newGrpList = undefined
|
|
|
|
local subLevel = (lodLevel - 1)
|
|
|
|
for subGrp in GrpList do
|
|
subGrp.TreeLevelCorrector subLevel firstRecurse:False
|
|
|
|
OK
|
|
),
|
|
|
|
-- Used by viewport-draw callback to draw group's overlays:
|
|
fn viewportDraw selected:false tagItem: showBoxes:True showShapes:True =
|
|
(
|
|
if (tagIdx == undefined) do return false
|
|
|
|
local dataTags = ::RsLodCombinerTool.dataTags
|
|
|
|
if (tagItem == unsupplied) do
|
|
(
|
|
tagItem = dataTags[tagIdx]
|
|
)
|
|
if tagItem.selected do
|
|
(
|
|
selected = true
|
|
)
|
|
|
|
-- Draw for this node:
|
|
if tagItem.viewShow and (not tagItem.isHidden) and (lodObjName != undefined) and (blockPos != undefined) do
|
|
(
|
|
if (boundsSize == Undefined) do
|
|
this.UpdateBounds()
|
|
|
|
if (boundsSize == Undefined) do
|
|
return False
|
|
|
|
local showColour = if selected then tagItem.viewportSelColour else tagItem.viewportColour
|
|
|
|
-- Show higher lod-levels as slightly darker:
|
|
for n = 2 to genLodLevel do
|
|
(
|
|
showColour *= 0.9
|
|
)
|
|
|
|
-- Draw LOD/grid-blocks:
|
|
local shapeLines = #()
|
|
if (showBoxes) or (showShapes) do
|
|
(
|
|
if (showBoxes) do
|
|
(
|
|
if (boundsSize == Undefined) do
|
|
this.UpdateBounds()
|
|
|
|
local outlineCorners = for pos in _boundsOutline collect (gw.wtransPoint pos)
|
|
append shapeLines outlineCorners
|
|
|
|
-- Draw additional marker if bounding box is zero-sized:
|
|
if ((distance boundsMin boundsMax) < 0.1) do
|
|
(
|
|
gw.wMarker (gw.wtransPoint boundsMin) #hollowBox color:showColour
|
|
)
|
|
|
|
-- If group has a containerLodParent, represent that:
|
|
if (containerLodParent != undefined) and (isValidNode containerLodParent.node) do
|
|
(
|
|
local contLodClr = containerLodParent.node.wireColor
|
|
contLodClr = ((3 * contLodClr) + white) / 4
|
|
|
|
local zPos = boundsMin.z
|
|
|
|
local contLodMin = containerLodParent.node.min
|
|
local contLodMax = containerLodParent.node.max
|
|
local drawContLodCorners = for cornerPos in #(contLodMin, [contLodMin.x,contLodMax.y,0], contLodMax, [contLodMax.x,contLodMin.y,0]) collect
|
|
(
|
|
cornerPos.z = zPos
|
|
gw.wtransPoint cornerPos
|
|
)
|
|
|
|
local drawBlockPos = (gw.wtransPoint blockPos)
|
|
|
|
-- Draw lines from box-corners to closest group-outline corners:
|
|
gw.setColor #line (contLodClr * 0.9)
|
|
for n = 1 to 4 do
|
|
(
|
|
local boxCorner = drawContLodCorners[n]
|
|
local cornerDists = for corner in outlineCorners collect (distance boxCorner corner)
|
|
local nearCorner = outlineCorners[findItem cornerDists (aMin cornerDists)]
|
|
|
|
gw.wPolyline #(boxCorner, nearCorner) False
|
|
)
|
|
|
|
gw.setColor #line contLodClr
|
|
gw.wPolyline drawContLodCorners true
|
|
)
|
|
)
|
|
|
|
if (showShapes) and (blockRadius != undefined) do
|
|
(
|
|
local shapeCorners
|
|
|
|
case blockType of
|
|
(
|
|
#box:
|
|
(
|
|
shapeCorners = for cornerPos in #([-1,-1,0], [1,-1,0], [1,1,0], [-1,1,0]) collect
|
|
(
|
|
gw.wtransPoint (blockPos + (blockRadius * cornerPos))
|
|
)
|
|
)
|
|
#hex:
|
|
(
|
|
local distA = blockRadius
|
|
local distB = (0.5 * blockRadius)
|
|
local distC = hexQuadTriHeight
|
|
|
|
shapeCorners = for cornerPos in #([-distA,0,0], [-distB,distC,0], [distB,distC,0], [distA,0,0], [distB,-distC,0], [-distB,-distC,0]) collect
|
|
(
|
|
gw.wtransPoint (blockPos + cornerPos)
|
|
)
|
|
)
|
|
#tri:
|
|
(
|
|
shapeCorners = for cornerPos in triVerts collect
|
|
(
|
|
gw.wtransPoint cornerPos
|
|
)
|
|
)
|
|
)
|
|
|
|
if (shapeCorners != undefined) do
|
|
(
|
|
append shapeLines shapeCorners
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Draw shapes:
|
|
local multiLine = (genLodLevel > 1)
|
|
local midPos = (gw.wtransPoint blockPos)
|
|
|
|
for shapeNum = 1 to shapeLines.count do
|
|
(
|
|
local corners = shapeLines[shapeNum]
|
|
local useColour = showColour
|
|
|
|
-- Show bounding-boxes slightly darker than block-shapes:
|
|
if (showBoxes) and (shapeNum == 1) do
|
|
(
|
|
useColour *= 0.85
|
|
)
|
|
|
|
-- Find corner-offsets for additional block-outline lines:
|
|
local cornerOffsets
|
|
if multiLine do
|
|
(
|
|
local vecCorners = #(corners[corners.count])
|
|
join vecCorners corners
|
|
|
|
local vecs = for n = 2 to vecCorners.count collect
|
|
(
|
|
vecCorners[n] - vecCorners[n - 1]
|
|
)
|
|
append vecs vecs[1]
|
|
|
|
cornerOffsets = for n = 1 to corners.count collect
|
|
(
|
|
local vecA = vecs[n]
|
|
local vecB = vecs[n + 1]
|
|
3 * normalize [-(vecA.Y + vecB.Y), (vecA.X + vecB.X), 0]
|
|
)
|
|
)
|
|
|
|
for lineNum = 1 to genLodLevel do
|
|
(
|
|
gw.setColor #line useColour
|
|
|
|
local drawCorners = corners
|
|
gw.wPolyline drawCorners true
|
|
|
|
if multiLine do
|
|
(
|
|
-- Draw each extra line slightly darker than the previous one:
|
|
useColour *= 0.9
|
|
|
|
-- Expand corners out by offset-amount:
|
|
for n = 1 to corners.count do
|
|
(
|
|
corners[n] += cornerOffsets[n]
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Draw centred text if node is selected
|
|
if selected do
|
|
(
|
|
local textMid = (GetTextExtent lodObjName) / 2
|
|
midPos.X -= textMid.X
|
|
midPos.Y += textMid.Y
|
|
|
|
gw.wtext midPos lodObjName color:showColour
|
|
)
|
|
)
|
|
|
|
-- Now draw sub-nodes - weight the ordering to put selected/out-of-sync items earlier:
|
|
local weightedSubItems = for item in _groupList where (item.tagIdx != undefined) collect
|
|
(
|
|
local subItemTag = dataTags[item.tagIdx]
|
|
local drawWeight = 0
|
|
|
|
if subItemTag.selected do (drawWeight += 5)
|
|
case (subItemTag.GetData()).lodSynced of
|
|
(
|
|
undefined:(drawWeight -= 1)
|
|
false:(drawWeight += 1)
|
|
)
|
|
|
|
dataPair tagItem:subItemTag drawWeight:drawWeight
|
|
)
|
|
|
|
-- Don't keep this big array around longer than it needs to be...
|
|
dataTags = undefined
|
|
|
|
-- Order subitems in order
|
|
fn orderItems v1 v2 = (v1.drawWeight - v2.drawWeight)
|
|
qsort weightedSubItems orderItems
|
|
|
|
-- Now draw subitems:
|
|
for item in weightedSubItems do
|
|
(
|
|
(item.tagItem.GetData()).viewportDraw tagItem:item.tagItem showBoxes:showBoxes showShapes:showShapes --selected:selected
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Top-level structure used to define LodCombiner per map:
|
|
struct RsLodCombinerMap
|
|
(
|
|
Private
|
|
_parentData, -- Always undefined, supplied for compatibility with other data-structs
|
|
_residentReasons = #(), -- Why where these objects made Resident?
|
|
_groupList,
|
|
_propsPerLodCounts = #(), -- Collects number of LOD-blocks lodding different numbers of props
|
|
|
|
Public
|
|
dataNode, -- Helper-object used to store this map's data
|
|
contNode, -- DataNode's container
|
|
|
|
childBlockType = #none,
|
|
childBlockRadius = -1,
|
|
|
|
groupName = "combo", -- Used by new subgroups looking for a parent-name
|
|
mapName = "undefined",
|
|
fullMapName = "undefined",
|
|
tagIdx, -- Object's dataTag index, as collected in RsLodCombinerTool.dataTags
|
|
|
|
objCount = 0, -- Total number of subobjects
|
|
lodObjCount = 0, -- Total number of generated lod-objects
|
|
|
|
|
|
treeHash = 0,
|
|
|
|
lodLevelsUsed,
|
|
lodLevelsCount,
|
|
genLodLevel = 0, -- Map-structs don't generate specific lod-level objects
|
|
|
|
boundsSize, boundsMin, boundsMax,
|
|
|
|
doResidents = True, -- Enables Memory Resident listing for this map
|
|
residentRefNums = #{}, -- Object-ref indexes that make up most of map's props
|
|
residentDistLimits = #(), -- Resident props are only added to a block with matching lodDistIdx (a prop's lodDistIdx will be index that is below the next highest limit)
|
|
|
|
lodsSynced, -- Set to true if group-lods are all up-to-date
|
|
|
|
expanded = true,
|
|
|
|
needsLoad = False, -- Used by updateMapData function
|
|
|
|
-- Accessor-functions for private values:
|
|
fn GetParentData = (return _parentData),
|
|
fn GetGroupList = (return _groupList),
|
|
fn GetResidentReasons = (return _residentReasons),
|
|
fn GetPropsPerLodCounts = (return _propsPerLodCounts),
|
|
|
|
-- This is the map's root-structure, so this function returns itself:
|
|
fn GetRootData =
|
|
(
|
|
return This
|
|
),
|
|
|
|
-- Gets combined list of map's subgroup/block list-items:
|
|
fn getSubGroups recurse:false =
|
|
(
|
|
local outList = #()
|
|
Join outList _groupList
|
|
|
|
if recurse do
|
|
(
|
|
for item in _groupList do
|
|
(
|
|
join outList (item.getSubGroups recurse:true)
|
|
)
|
|
)
|
|
|
|
return outList
|
|
),
|
|
|
|
-- Get number of objects for each lod-level (item 1 is for non-lod objects)
|
|
-- (used to properly update progress-bar during lod-generation)
|
|
fn SetLodLevelsCount =
|
|
(
|
|
this.objCount = 0
|
|
this.lodObjCount = 0
|
|
|
|
this.lodLevelsUsed = #{}
|
|
this.lodLevelsCount = for n = 1 to 4 collect 0
|
|
|
|
for item in _groupList do
|
|
(
|
|
this.objCount += item.objCount
|
|
this.lodObjCount += item.lodObjCount
|
|
|
|
this.lodLevelsUsed += item.lodLevelsUsed
|
|
|
|
local itemLevels = item.lodLevelsCount
|
|
for n = 1 to 4 do
|
|
this.lodLevelsCount[n] += itemLevels[n]
|
|
)
|
|
|
|
return OK
|
|
),
|
|
|
|
-- Generate combined hash for map's lod-source objects:
|
|
fn getSourceHash getFresh:true =
|
|
(
|
|
local hashes = for item in _groupList collect (item.getSourceHash getFresh:getFresh)
|
|
|
|
if (hashes.count == 0) then 0 else (getHashValue hashes 1)
|
|
),
|
|
|
|
|
|
-- Removes objitems from their current lod-blocks, and re-adds them to their lod-blocks' parents.
|
|
-- This ensures that the objects are in suitable lod-blocks, via the block-assigning methods.
|
|
fn ReblockObjLods objItems =
|
|
(
|
|
if (objItems.count == 0) do
|
|
return OK
|
|
|
|
--Format ".ReblockObjLods - %\n" (gRsDebugStack.GetCallerLine())
|
|
|
|
PushPrompt "Updating LOD-blocking..."
|
|
|
|
objItems = makeUniqueArray objItems
|
|
|
|
-- Parent-groups for re-blocking, and the objects to be re-added to each group:
|
|
local parentGrps = #()
|
|
local grpObjs = #()
|
|
|
|
for item in objItems do
|
|
(
|
|
-- Find lod-group-parent for object:
|
|
local parGrp = (Item.GetParentData()).GetParentData()
|
|
local grpNum = findItem parentGrps parGrp
|
|
|
|
if (grpNum == 0) do
|
|
(
|
|
append parentGrps parGrp
|
|
append grpObjs #()
|
|
|
|
grpNum = parentGrps.count
|
|
)
|
|
|
|
-- Collect objects per lod-group:
|
|
append grpObjs[grpNum] item.node
|
|
)
|
|
|
|
for grpNum = 1 to parentGrps.count do
|
|
(
|
|
local grp = parentGrps[grpNum]
|
|
local objs = grpObjs[grpNum]
|
|
|
|
local msg = stringStream ""
|
|
format "Updating group: % (changing % objects)\n" grp.groupName objs.count to:msg
|
|
|
|
pushPrompt (msg as string)
|
|
|
|
-- Remove objects from their current groups, then re-add:
|
|
grp.removeObjs objs checkAll:False removeEmpty:False
|
|
|
|
local added = #{}
|
|
grp.AddObjs objs done:&added
|
|
|
|
popPrompt()
|
|
)
|
|
|
|
-- Update group-values:
|
|
for grp in _groupList do
|
|
grp.UpdateVals refreshObjVals:True removeEmpty:False
|
|
|
|
PopPrompt()
|
|
),
|
|
|
|
-- Returns list of objects in map's groups:
|
|
fn getObjs recurse:true info:false =
|
|
(
|
|
if not recurse do return #()
|
|
|
|
local grpObjs = #()
|
|
|
|
for grpInfo in _groupList do
|
|
(
|
|
join grpObjs (grpInfo.getObjs info:info)
|
|
)
|
|
|
|
return grpObjs
|
|
),
|
|
|
|
-- Find Lod Distance Index for a prop, used to avoid having Resident blocks containing props with wildly different lod-distances.
|
|
-- propLodDistIdx is index below where (DistLimit > propLodDist)
|
|
fn getPropLodDistIdx obj =
|
|
(
|
|
local propLodDist = (obj.scale.z * obj.refDef.comboLod.lodDistance)
|
|
|
|
local retVal = 0
|
|
for idx = 1 to residentDistLimits.count while (residentDistLimits[idx] < propLodDist) do
|
|
(
|
|
retVal = idx
|
|
)
|
|
|
|
return retVal
|
|
),
|
|
|
|
-- Finds objects in mixed-objectname lodgroups, and marks them for reblocking if they're not meant to be so:
|
|
fn findMixedNameBlocks mapObjItems:#() reblockObjItems:#() =
|
|
(
|
|
for objItem in mapObjItems do
|
|
(
|
|
local lodGrp = objItem.GetParentData()
|
|
|
|
if (lodGrp.blockByProp) and (not lodGrp.isResidentBlock) and (not objItem.isResidentProp) do
|
|
(
|
|
local modelName = objItem.node.objectName
|
|
|
|
if (lodGrp.blockByPropName == undefined) then
|
|
(
|
|
lodGrp.blockByPropName = (toLower modelName)
|
|
)
|
|
else
|
|
(
|
|
if not matchPattern (modelName) pattern:(lodGrp.blockByPropName) do
|
|
(
|
|
append reblockObjItems objItem
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
return OK
|
|
),
|
|
|
|
-- Generate list of 50x50m grid-points covered by this object's lod-circle:
|
|
fn getPropGrids objItem gridSize:50 =
|
|
(
|
|
local objPos = objItem.node.pos
|
|
local propGridPos = (objPos / gridSize)
|
|
propGridPos = [integer propGridPos.X, integer propGridPos.Y]
|
|
propGridPos *= gridSize
|
|
|
|
local radius = objItem.getLodDistance()
|
|
|
|
-- Collect list of possible grid-points for prop's circle to cover:
|
|
local xPosList = for x = propGridPos.x to (propGridPos.x + radius + gridSize) by gridSize collect x
|
|
join xPosList (for x = (propGridPos.x - gridSize) to (propGridPos.x - radius - gridSize) by -gridSize collect x)
|
|
local yPosList = for y = propGridPos.y to (propGridPos.y + radius + gridSize) by gridSize collect y
|
|
join yPosList (for y = (propGridPos.y - gridSize) to (propGridPos.y - radius - gridSize) by -gridSize collect y)
|
|
|
|
local propGrids = #()
|
|
for x in xPosList do
|
|
(
|
|
join propGrids \
|
|
(
|
|
for y in yPosList collect
|
|
(
|
|
[x,y]
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Ignore grids that are outside prop's lod-circle:
|
|
local propPos = [objPos.X, objPos.Y]
|
|
propGrids = for pos in propGrids where (distance pos propPos < radius) or (pos == propGridPos) collect pos
|
|
|
|
return propGrids
|
|
),
|
|
|
|
-- Update map's ComboLodder-prop bounds
|
|
fn UpdateBounds updateGrpBounds:True =
|
|
(
|
|
boundsSize = boundsMin = boundsMax = undefined
|
|
|
|
-- Only bother updating group-boundaries if requested:
|
|
if updateGrpBounds do
|
|
(
|
|
for grp in _groupList do
|
|
grp.UpdateBounds()
|
|
)
|
|
|
|
local minMaxList = #()
|
|
for grp in _groupList where (grp.boundsMin != undefined) do
|
|
Join minMaxList #(grp.boundsMin, grp.boundsMax)
|
|
|
|
RsGetBBox minMaxList &boundsMin &boundsMax
|
|
boundsSize = boundsMax - boundsMin
|
|
|
|
return OK
|
|
),
|
|
|
|
-- Find common refs, and generate ResidentRefNums list for this map:
|
|
fn SetupResidents =
|
|
(
|
|
--Format ".SetupResidents - %\n" (gRsDebugStack.GetCallerLine())
|
|
|
|
local resGridCountRatio = gRsLodCombinerVals.resGridCountRatio
|
|
local resPropCountRatio = gRsLodCombinerVals.resPropCountRatio
|
|
local resTinyBlockCount = gRsLodCombinerVals.resTinyBlockCount
|
|
local resTinyBlockSize = gRsLodCombinerVals.resTinyBlockSize
|
|
|
|
local AreaZoneReason = "Area-Zone Resident"
|
|
local gridCountReason = "Area-count >" + (formattedPrint (100 * resGridCountRatio)) + "%"
|
|
local propCountReason = "Prop-count >" + (formattedPrint (100 * resPropCountRatio)) + "%"
|
|
local tinyBlockReason = "Tiny-block count >" + (formattedPrint resTinyBlockCount)
|
|
|
|
-- Collect list of all object-nodes used by ComboLodder for this map:
|
|
local mapObjItems = (getObjs info:True)
|
|
|
|
-- Clear current Resident data from refs:
|
|
(
|
|
mapObjItems.isResidentProp = False
|
|
residentRefNums.count = 0
|
|
residentDistLimits.count = 0
|
|
_residentReasons.count = 0
|
|
)
|
|
|
|
local timeStart = timeStamp()
|
|
|
|
pushPrompt "Organising Memory Resident props..."
|
|
|
|
-- List props-nodes per model (for lodding props)
|
|
local allObjsCount = 0
|
|
local usedRefNums = #()
|
|
local usedRefObjItems = #()
|
|
local usedRefGrids = #()
|
|
if doResidents do
|
|
(
|
|
for objItem in mapObjItems where (objItem.sourceHash != 0) do
|
|
(
|
|
local refNum = objItem.refNum
|
|
local findNum = findItem usedRefNums refNum
|
|
|
|
if (findNum == 0) do
|
|
(
|
|
append usedRefNums refNum
|
|
append usedRefObjItems #()
|
|
append usedRefGrids #()
|
|
findNum = usedRefNums.count
|
|
)
|
|
|
|
append usedRefObjItems[findNum] objItem
|
|
join usedRefGrids[findNum] (getPropGrids objItem)
|
|
|
|
allObjsCount += 1
|
|
)
|
|
)
|
|
|
|
-- Get resident-props list for Area zones intersecting with this map's bounds:
|
|
local areaResidents = #()
|
|
if doResidents do
|
|
(
|
|
-- Find bounding-box for map's comboloddy objects:
|
|
this.UpdateBounds updateGrpBounds:True
|
|
|
|
-- Get list of models made resident for Area-zones intersecting with this map:
|
|
areaResidents = gRsMemResidentLists.GetAreaPropsForBox boundsMin boundsMax
|
|
areaResidents = for propName in areaResidents collect (ToLower PropName)
|
|
)
|
|
|
|
-- Are any of this map's combolodder-prop on this list?
|
|
if (areaResidents.count != 0) do
|
|
(
|
|
local dlcPrefix = (ToLower gRsProject.assetPrefix.mapPrefix)
|
|
|
|
for idx = 1 to usedRefNums.count do
|
|
(
|
|
local RefNum = UsedRefNums[idx]
|
|
|
|
-- Get ref-data from RSref database:
|
|
local thisRef = rsRefData.refs[refNum]
|
|
local refName = (ToLower thisRef.name)
|
|
|
|
-- Add prefix if this is a DLC object-name,
|
|
-- because that's how it'll be in the residents-list metafile
|
|
if rSrefData.fileIsDLC[thisRef.maxfileNum] do
|
|
refName = (dlcPrefix + refName)
|
|
|
|
gRsProject.assetPrefix.mapPrefix
|
|
|
|
-- Is this model-name on the area-residents list?
|
|
if (FindItem AreaResidents RefName != 0) do
|
|
(
|
|
-- Mark this ref's objects as Resident if it's on the list:
|
|
local RefObjItems = usedRefObjItems[idx]
|
|
|
|
refObjItems.isResidentProp = True
|
|
residentRefNums[refNum] = True
|
|
_residentReasons[refNum] = areaZoneReason
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Set models as resident if they're used by a certain ratio of map's props:
|
|
if doResidents and (resPropCountRatio > 0) do
|
|
(
|
|
for idx = 1 to usedRefNums.count do
|
|
(
|
|
local refNum = usedRefNums[idx]
|
|
|
|
-- No need to check models that have already been marked as Resident:
|
|
if (not residentRefNums[refNum]) do
|
|
(
|
|
local refObjItems = usedRefObjItems[idx]
|
|
local refObjRatio = (float refObjItems.count / allObjsCount)
|
|
|
|
if (refObjRatio > resPropCountRatio) do
|
|
(
|
|
refObjItems.isResidentProp = True
|
|
residentRefNums[refNum] = True
|
|
_residentReasons[refNum] = propCountReason
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Set models as resident if they cover a certain ratio of map's grid-points:
|
|
if doResidents and (resGridCountRatio > 0) do
|
|
(
|
|
local allGridsCount
|
|
|
|
-- Find out total number of unique grid-points covered by lod-props:
|
|
(
|
|
local allGrids = #()
|
|
|
|
for idx = 1 to usedRefGrids.count do
|
|
(
|
|
-- Make sub-lists unique:
|
|
usedRefGrids[idx] = makeUniqueArray usedRefGrids[idx]
|
|
|
|
join allGrids usedRefGrids[idx]
|
|
)
|
|
|
|
allGridsCount = (makeUniqueArray allGrids).count
|
|
allGrids.count = 0
|
|
)
|
|
|
|
for idx = 1 to usedRefNums.count do
|
|
(
|
|
local refNum = usedRefNums[idx]
|
|
|
|
-- No need to check models that have already been marked as Resident:
|
|
if (not residentRefNums[refNum]) do
|
|
(
|
|
local refObjItems = usedRefObjItems[idx]
|
|
local refObjGrids = usedRefGrids[idx]
|
|
|
|
local thisRatio = (float refObjGrids.count / allGridsCount)
|
|
|
|
if (thisRatio > resGridCountRatio) do
|
|
(
|
|
refObjItems.isResidentProp = True
|
|
residentRefNums[refNum] = True
|
|
_residentReasons[refNum] = gridCountReason
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
-- These objects will be reblocked:
|
|
local reblockObjItems = #()
|
|
|
|
-- Set up list of objects that will need to be reblocked now:
|
|
-- Collect all objects that are marked as Resident, or are in Resident lod-blocks:
|
|
join reblockObjItems (for objItem in mapObjItems where objItem.isResidentProp or (objItem.GetParentData()).isResidentBlock collect objItem)
|
|
|
|
--reblockObjItems = makeUniqueArray reblockObjItems
|
|
|
|
popPrompt()
|
|
|
|
-- Find objects in mixed-name lod-blocks that shouldn't be so:
|
|
findMixedNameBlocks mapObjItems:mapObjItems reblockObjItems:reblockObjItems
|
|
|
|
-- Clear this array, we don't need it for now...
|
|
mapObjItems.count = 0
|
|
|
|
-------------------------------------
|
|
-- Reblock marked object-items --
|
|
-------------------------------------
|
|
this.ReblockObjLods reblockObjItems
|
|
reblockObjItems.count = 0
|
|
-------------------------------------
|
|
|
|
-------------------------------------------------------------------
|
|
-- Now find non-resident models that appear in a lot of --
|
|
-- one/two-prop blocks, and make those resident too: --
|
|
-------------------------------------------------------------------
|
|
if doResidents and (resTinyBlockCount > 0) do
|
|
(
|
|
-- Get new obj-items list, ignoring already-resident models:
|
|
local mapObjItems = for objItem in (getObjs info:True) where (not objItem.isResidentProp) collect objItem
|
|
|
|
local refsNumsUsed = #()
|
|
local refCounts = #()
|
|
local checkedLodBlocks = #()
|
|
|
|
local totalLodBlockCount = 0.0
|
|
|
|
for objItem in mapObjItems do
|
|
(
|
|
local parentBlock = (objItem.GetParentData())
|
|
|
|
if (findItem checkedLodBlocks parentBlock == 0) do
|
|
(
|
|
append checkedLodBlocks parentBlock
|
|
|
|
local lodObjItems = for lodObjItem in (parentBlock.getObjs info:True) where (lodObjItem.sourceHash != 0) collect lodObjItem
|
|
|
|
if (lodObjItems.count > 0) do
|
|
(
|
|
totalLodBlockCount += 1
|
|
)
|
|
|
|
-- Count models for blocks with 1 or two props:
|
|
if (lodObjItems.count <= resTinyBlockSize) do
|
|
(
|
|
local lodRefNums = #()
|
|
|
|
for objItem in lodObjItems do
|
|
(
|
|
local refDef = objItem.node.refDef
|
|
|
|
-- Only count a refDef once per block:
|
|
if (appendIfUnique lodRefNums refDef) do
|
|
(
|
|
local refNum = objItem.refNum
|
|
local findNum = findItem refsNumsUsed refNum
|
|
|
|
if (findNum == 0) do
|
|
(
|
|
append refsNumsUsed refNum
|
|
append refCounts (dataPair refNum:refNum count:0)
|
|
findNum = refsNumsUsed.count
|
|
)
|
|
|
|
refCounts[findNum].count += 1
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
checkedLodBlocks.count = 0
|
|
|
|
-- Find models that show in a lot of small lod-blocks, and set them to Resident:
|
|
local newResidentsCount = 0
|
|
for item in refCounts where (item.count > resTinyBlockCount) do
|
|
(
|
|
residentRefNums[item.refNum] = True
|
|
_residentReasons[item.refNum] = tinyBlockReason
|
|
|
|
newResidentsCount += 1
|
|
)
|
|
|
|
refCounts.count = 0
|
|
|
|
-- Set matching objects as Resident:
|
|
if (newResidentsCount != 0) do
|
|
(
|
|
local newResidents = for objItem in mapObjItems where (objItem.refNum != 0) and residentRefNums[objItem.refNum] collect objItem
|
|
newResidents.isResidentProp = True
|
|
)
|
|
|
|
mapObjItems.count = 0
|
|
)
|
|
|
|
----------------------------------------------------------------------
|
|
-- Do a final reblocking session,
|
|
-- if non-resident objects are found in Resident blocks:
|
|
----------------------------------------------------------------------
|
|
(
|
|
local mapObjItems = (getObjs info:True)
|
|
|
|
-- Setup up distance-limits for lod-distance index:
|
|
(
|
|
-- Reset object lodDistIdx values:
|
|
residentDistLimits.count = 0
|
|
mapObjItems.lodDistIdx = 0
|
|
|
|
-- Find lod-distances for all of map's Residents:
|
|
local lodDistVals = for objItem in mapObjItems where objItem.isResidentProp collect (objItem.getLodDistance())
|
|
|
|
-- Work out distance-limits, so as to avoid having lod-blocks with distance-ranges greater than maxDistRange
|
|
if (lodDistVals.count != 0) do
|
|
(
|
|
local maxDistRange = gRsLodCombinerVals.resMaxDistRange
|
|
|
|
local minLodDist = aMin lodDistVals
|
|
local maxLodDist = aMax lodDistVals
|
|
local lodDistRange = (maxLodDist - minLodDist)
|
|
|
|
if (lodDistRange > maxDistRange) do
|
|
(
|
|
local idxCount = ceil (lodDistRange / maxDistRange)
|
|
local distIncrement = lodDistRange / idxCount
|
|
|
|
-- Set distance-increments:
|
|
residentDistLimits = for lodDist = (minLodDist + distIncrement) to (maxLodDist - distIncrement) by distIncrement collect lodDist
|
|
)
|
|
)
|
|
|
|
-- Set lodDistIdx values for all Residents, using newly-collected residentDistLimits:
|
|
for objItem in mapObjItems where objItem.isResidentProp do
|
|
(
|
|
objItem.lodDistIdx = getPropLodDistIdx objItem.node
|
|
)
|
|
)
|
|
|
|
-- Collect objItems where object doesn't match parent's Resident flag, or its lodDistIdx doesn't match:
|
|
local reblockObjItems = for objItem in mapObjItems where
|
|
(
|
|
local isResidentProp = objItem.isResidentProp
|
|
local isResidentBlock = (objItem.GetParentData()).isResidentBlock
|
|
(isResidentProp != isResidentBlock) or (isResidentProp and (objItem.lodDistIdx != (objItem.GetParentData()).lodDistIdx))
|
|
) collect objItem
|
|
|
|
mapObjItems.count = 0
|
|
|
|
-- Assign objects to different lod-blocks if they don't match their current block-type:
|
|
if (reblockObjItems.count != 0) do
|
|
(
|
|
this.ReblockObjLods reblockObjItems
|
|
reblockObjItems.count = 0
|
|
)
|
|
)
|
|
|
|
return OK
|
|
),
|
|
|
|
-- Gets number lod-objects for each props-per-lod-group count...
|
|
fn SetPropsPerLodCounts =
|
|
(
|
|
_propsPerLodCounts = #()
|
|
|
|
for grp in _groupList do
|
|
(
|
|
local grpCounts = grp.GetPropsPerLodCounts()
|
|
for n = 1 to grpCounts.count do
|
|
(
|
|
local thisVal = grpCounts[n]
|
|
|
|
if (thisVal != undefined) do
|
|
(
|
|
if (_propsPerLodCounts[n] == undefined) do
|
|
(
|
|
_propsPerLodCounts[n] = 0
|
|
)
|
|
|
|
_propsPerLodCounts[n] += thisVal
|
|
)
|
|
)
|
|
)
|
|
|
|
return OK
|
|
),
|
|
|
|
-- Remove gridded subgroups if they have no objects:
|
|
fn RemoveEmptyGridBlocks =
|
|
(
|
|
for grpItem in _groupList do
|
|
grpItem.RemoveEmptyGridBlocks()
|
|
),
|
|
|
|
-- Update container's group-list sync values:
|
|
fn UpdateVals refreshObjVals:False removeEmpty:True =
|
|
(
|
|
local timeStart = timeStamp()
|
|
|
|
-- Update group-values:
|
|
for grp in _groupList do
|
|
grp.UpdateVals refreshObjVals:refreshObjVals
|
|
|
|
-- Used to track overall changes to sub-nodes:
|
|
local treeHashes = for item in _groupList collect item.treeHash
|
|
treeHash = getHashValue treeHashes 1
|
|
|
|
-- Find/organise Memory Resident meshes for map, reblocking if necessary:
|
|
this.SetupResidents()
|
|
|
|
-- Remove any empty lod-blocks:
|
|
if removeEmpty do
|
|
this.RemoveEmptyGridBlocks()
|
|
|
|
-- Set object-counts, used to properly update progress-bar during lod-generation:
|
|
this.SetLodLevelsCount()
|
|
|
|
-- Collect props-per-lod counts from subgroups, used to show 1-prop/2-prop block-values:
|
|
this.SetPropsPerLodCounts()
|
|
|
|
-- Update map's bounds:
|
|
this.UpdateBounds updateGrpBounds:False
|
|
|
|
-- Set lodSynced false if any subitems are false, or true if any are true.
|
|
-- Stays as undefined if none are specified.
|
|
lodsSynced = undefined
|
|
for item in _groupList where (item.lodsSynced != undefined) while (lodsSynced != false) do
|
|
(
|
|
lodsSynced = item.lodsSynced
|
|
)
|
|
),
|
|
|
|
-- Returns list of lod-objects generated by map's groups:
|
|
fn getLodObjs info:false recurse:true =
|
|
(
|
|
local lodItems = #()
|
|
|
|
if recurse do
|
|
(
|
|
for item in _groupList do
|
|
(
|
|
join lodItems (item.getLodObjs info:info recurse:true)
|
|
)
|
|
)
|
|
|
|
return lodItems
|
|
),
|
|
|
|
-- Used by viewport-draw callback to draw container's overlays:
|
|
fn viewportDraw selected:false tagItem: showBoxes:True showShapes:True =
|
|
(
|
|
if (_groupList != undefined) and (tagIdx != undefined) do
|
|
(
|
|
if (tagItem == unsupplied) do
|
|
(
|
|
tagItem = ::RsLodCombinerTool.dataTags[tagIdx]
|
|
)
|
|
if tagItem.selected do
|
|
(
|
|
selected = true
|
|
)
|
|
|
|
for item in _groupList do
|
|
(
|
|
item.viewportDraw showBoxes:showBoxes showShapes:showShapes -- selected:selected
|
|
)
|
|
)
|
|
),
|
|
|
|
-- Set filename for xml-file used to store Memory Resident object-list:
|
|
fn getResidentFilename =
|
|
(
|
|
if (mapName == undefined) then (return undefined) else
|
|
(
|
|
return (gRsMemResidentLists.GetMetafilePath() + mapName + ".meta")
|
|
)
|
|
),
|
|
|
|
-- Work out bounding-box for use in Memory Resident metafile:
|
|
fn getResidentBounds =
|
|
(
|
|
-- Get list of all sub-grps:
|
|
local subGrps = #()
|
|
Join subGrps (this.GetGroupList())
|
|
|
|
local grpNum = 0
|
|
while (grpNum < subGrps.count) do
|
|
(
|
|
grpNum += 1
|
|
join subGrps (subGrps[grpNum].GetGroupList())
|
|
)
|
|
|
|
-- Collect lod-objects for Resident Model lod-nodes:
|
|
local resLodObjs = #()
|
|
for grp in subGrps where grp.isResidentBlock do
|
|
(
|
|
join resLodObjs (grp.getLodObjs recurse:False)
|
|
)
|
|
subGrps.count = 0
|
|
|
|
-- Collect min/max values for lod-spheres:
|
|
local minMaxList = #()
|
|
for obj in resLodObjs do
|
|
(
|
|
local lodPos = obj.pos
|
|
local lodRadius = (getattr obj idxChildLodDistance)
|
|
local lodOffset = ([1,1,1] * lodRadius)
|
|
|
|
join minMaxList #((lodPos + lodOffset), (lodPos - lodOffset))
|
|
)
|
|
|
|
-- Find overall bounding-box:
|
|
local minVal = undefined
|
|
local maxVal = undefined
|
|
local success = RsGetBBox minMaxList &minVal &maxVal
|
|
|
|
-- If that failed, use node-bounds as fallback:
|
|
if not success do
|
|
(
|
|
minVal = boundsMin
|
|
maxVal = boundsMax
|
|
)
|
|
|
|
return (dataPair min:minVal max:maxVal)
|
|
),
|
|
|
|
-- Write Memory Resident objects-list out to xml:
|
|
fn storeResidents =
|
|
(
|
|
local xmlFilename = GetResidentFilename()
|
|
|
|
if (xmlFilename == undefined) do return False
|
|
|
|
if (RsIsFileReadOnly xmlFilename) do
|
|
(
|
|
format "Can't export Residents List, file is readonly: %\n" xmlFilename
|
|
return False
|
|
)
|
|
|
|
-- Get bounds for resident-models used in scene
|
|
local resBounds = GetResidentBounds()
|
|
|
|
-- Get list of prop-names, with prefix added for DLC-only propss
|
|
local propNames = #()
|
|
for refNum in residentRefNums do
|
|
(
|
|
local refDef = RsRefData.refs[refNum]
|
|
local propName = refDef.name
|
|
|
|
-- If this is a DLC prop, add the project-prefix to its name
|
|
if RSrefData.fileIsDLC[refdef.maxfileNum] do
|
|
propName = (gRsProject.assetPrefix.mapPrefix + propName)
|
|
|
|
AppendIfUnique propNames propName
|
|
)
|
|
|
|
-- Generate ResidentZone struct for export:
|
|
local ResidentZone = (RsMemRes_Zone ZoneName:MapName Filename:XmlFilename MinPos:ResBounds.Min MaxPos:ResBounds.Max PropNames:PropNames)
|
|
ResidentZone.Export()
|
|
),
|
|
|
|
-- Store groups to XML string in dataNode's paramblock:
|
|
fn store =
|
|
(
|
|
local timeStart = timeStamp()
|
|
|
|
format "Storing ComboLod data:\n..%\n" mapName
|
|
|
|
undo off
|
|
(
|
|
-- Delete store-node if it has nothing to store, or its container-node is invalid:
|
|
if (_groupList == undefined) or (_groupList.count == 0) or (not isValidNode contNode) then
|
|
(
|
|
if (isValidNode dataNode) do
|
|
(
|
|
delete dataNode
|
|
)
|
|
)
|
|
else
|
|
(
|
|
-- Create data-node, if struct doesn't have one already:
|
|
if (not isValidNode dataNode) do
|
|
(
|
|
local newPos = copy contNode.pos
|
|
newPos.Z -= 10
|
|
|
|
dataNode = RsGenericDataNode dataClass:"ComboLodder" name:(uniqueName "_ComboLodder_") isHidden:true isFrozen:true pos:newPos
|
|
|
|
-- Put datanode in correct container, and make sure it's in the default layer:
|
|
(LayerManager.GetLayer 0).addNode dataNode
|
|
contNode.addNodeToContent dataNode
|
|
)
|
|
|
|
-- Create xml-writer:
|
|
local stringBuilder = dotNetObject "System.Text.StringBuilder"
|
|
local stringWriter = dotNetObject "System.IO.StringWriter" stringBuilder
|
|
local xmlWriter = dotNetObject "System.Xml.XmlTextWriter" stringWriter
|
|
xmlWriter.Formatting = xmlWriter.Formatting.Indented
|
|
xmlWriter.Namespaces = False
|
|
|
|
local newObjsList = #()
|
|
|
|
-- Create root-node:
|
|
xmlWriter.WriteStartElement "root"
|
|
|
|
-- Store header-data node:
|
|
(
|
|
xmlWriter.WriteStartElement "info"
|
|
xmlWriter.WriteAttributeString "mapname" (contNode.name)
|
|
|
|
-- Only save this if non-default:
|
|
if (doResidents != (RsLodCombinerMap()).doResidents) do
|
|
(
|
|
xmlWriter.WriteAttributeString "doResidents" (doResidents as string)
|
|
)
|
|
|
|
-- Store current version-number:
|
|
xmlWriter.WriteAttributeString "version" (gRsLodCombinerVals.dataVersion as string)
|
|
|
|
-- Find and write out the number of group-nodes being saved, for use by progress-bar on loading:
|
|
local grpNum = 0
|
|
local allGrps = for item in _groupList collect item
|
|
while grpNum < allGrps.count do
|
|
(
|
|
grpNum += 1
|
|
join allGrps (allGrps[grpNum].GetGroupList())
|
|
)
|
|
allGrps = undefined
|
|
xmlWriter.WriteAttributeString "groupCount" (grpNum as string)
|
|
|
|
xmlWriter.WriteEndElement()
|
|
)
|
|
|
|
-- Store map's slod-groups:
|
|
(
|
|
for groupItem in _groupList do
|
|
(
|
|
groupItem.store xmlWriter objsList:newObjsList
|
|
)
|
|
)
|
|
|
|
-- Close root-node:
|
|
xmlWriter.WriteEndElement()
|
|
|
|
xmlWriter.close()
|
|
stringWriter.close()
|
|
|
|
-- Store data to paramblock:
|
|
dataNode.setNodeList newObjsList
|
|
dataNode.dataString = stringBuilder.ToString()
|
|
)
|
|
)
|
|
|
|
format "..[took %s]\n" ((timeStamp() - timeStart) / 1000.0)
|
|
),
|
|
|
|
-- Parses dataNode's xml-string, retrieving group-data:
|
|
fn restore &cancelled: =
|
|
(
|
|
_groupList = #()
|
|
|
|
if (not isValidNode dataNode) or (dataNode.dataString == undefined) do
|
|
(
|
|
return false
|
|
)
|
|
|
|
local timeStart = timeStamp()
|
|
|
|
format "Parsing ComboLod data:\n..%\n" mapName
|
|
|
|
-- Read xml-data from dataString string:
|
|
local textReader = dotNetObject "System.IO.StringReader" dataNode.dataString
|
|
local xmlReader = dotNetObject "System.Xml.XmlTextReader" textReader
|
|
|
|
pushPrompt ("Parsing ComboLod group-data: " + mapName)
|
|
|
|
local maxLodLevel = gRsLodCombinerVals.lodSuffixes.count
|
|
|
|
local readVersion = 0
|
|
|
|
-- Reconstitute group-data from xml:
|
|
xmlReader.Read()
|
|
|
|
local doInfoPrint = True
|
|
|
|
if (xmlReader.NodeType == xmlReader.NodeType.Element) do
|
|
(
|
|
-- Read first element:
|
|
xmlReader.Read()
|
|
|
|
local doProgress = False
|
|
local progressVal = 0
|
|
local progressMax = 0
|
|
|
|
local stopLoop = False
|
|
local blankLoopNum = 0
|
|
|
|
while (not stopLoop) do
|
|
(
|
|
local nodeType = xmlReader.NodeType
|
|
stopLoop = (nodeType == nodeType.EndElement) or (nodeType == nodeType.None) or (blankLoopNum == 2)
|
|
|
|
if not stopLoop do
|
|
(
|
|
case xmlReader.name of
|
|
(
|
|
-- Map/version data:
|
|
"info":
|
|
(
|
|
blankLoopNum = 0
|
|
|
|
local getVal = xmlReader.Item["version"]
|
|
if (getVal != undefined) do
|
|
(
|
|
readVersion = (getVal as number)
|
|
|
|
if (readVersion > gRsLodCombinerVals.VersionNum) do
|
|
(
|
|
local msg = stringStream ""
|
|
format "WARNING: %'s ComboLodder data was saved with a later version of the tool!\n\n" mapName to:msg
|
|
format "Your version: %\nSaved version: %\n" gRsLodCombinerVals.VersionNum readVersion to:msg
|
|
format "\nPlease update to latest Wildwest files to get latest version of ComboLodder tool!" mapName to:msg
|
|
|
|
messageBox (msg as string) title:"WARNING: Out-of-date ComboLodder tool"
|
|
)
|
|
)
|
|
|
|
local getVal = xmlReader.Item["groupCount"]
|
|
|
|
if (getVal != undefined) do
|
|
(
|
|
doProgress = True
|
|
progressMax = (getVal as number)
|
|
format "..[% group-nodes]\n" progressMax
|
|
|
|
progressStart ("Parsing ComboLodder data:")
|
|
)
|
|
|
|
local getVal = xmlReader.Item["doResidents"]
|
|
if (getVal != undefined) do
|
|
(
|
|
doResidents = (getVal as booleanClass)
|
|
)
|
|
)
|
|
-- SLOD/LOD-groups:
|
|
"group":
|
|
(
|
|
blankLoopNum = 0
|
|
|
|
if doInfoPrint do
|
|
(
|
|
doInfoPrint = false
|
|
|
|
if (readVersion != gRsLodCombinerVals.dataVersion) do
|
|
(
|
|
format "..[Loading older-version data: Data:% Tool:%]\n" readVersion gRsLodCombinerVals.dataVersion
|
|
)
|
|
)
|
|
|
|
local doReblock = False
|
|
local newGroup = RsLodCombinerGroup.restore xmlReader objsList:(dataNode.getNodeList()) parentData:This readVersion:readVersion progressVal:&progressVal progressMax:progressMax cancelled:&cancelled doReblock:&doReblock
|
|
|
|
if (newGroup != DontCollect) do
|
|
Append _groupList newGroup
|
|
)
|
|
"":
|
|
(
|
|
-- Used to avoid infinite-looping errors:
|
|
blankLoopNum += 1
|
|
|
|
if (blankLoopNum == 2) do
|
|
(
|
|
print "POTENTIAL INFINITE LOOP AVOIDED"
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Read next element:
|
|
xmlReader.Read()
|
|
)
|
|
|
|
stopLoop = stopLoop or cancelled
|
|
)
|
|
|
|
if doProgress do
|
|
(
|
|
progressEnd(); pushPrompt ""; popPrompt()
|
|
)
|
|
)
|
|
|
|
xmlReader.Close()
|
|
textReader.Close()
|
|
|
|
if cancelled then
|
|
(
|
|
format "..[CANCELLED]\n"
|
|
_groupList.count = 0
|
|
)
|
|
else
|
|
(
|
|
-- Make sure that group-hierarchy matches their genLodLevel values:
|
|
pushPrompt "Correcting old data-tree hierarchies..."
|
|
RsLodCombinerGroup.treeLevelCorrector maxLodLevel grp:This
|
|
popPrompt()
|
|
|
|
this.RemoveEmptyGridBlocks()
|
|
|
|
local mapSlodGrps = for grp in (this.GetSubGroups recurse:True) where (grp.groupName != Undefined) collect grp
|
|
|
|
-- Clamp group-names that are too long:
|
|
if (mapName != undefined) do
|
|
(
|
|
local maxLength = (30 - mapName.count - 8)
|
|
pushPrompt "Renaming long-named groups..."
|
|
|
|
local longNameGrps = for grp in mapSlodGrps where (grp.groupName.count > maxLength ) collect grp
|
|
|
|
for grp in longNameGrps do
|
|
(
|
|
local newName = RsStripSuffixNums (subString grp.groupName 1 maxLength)
|
|
|
|
format "Renaming long-named group:\n %...\n" grp.groupName
|
|
grp.SetGrpNames #(grp) newName recurse:False
|
|
format " ...%\n" grp.groupName
|
|
)
|
|
|
|
popPrompt()
|
|
)
|
|
|
|
-- Fix any duplicated SLOD2/SLOD group-names
|
|
(
|
|
local usedGroupNames = #()
|
|
local renamed = False
|
|
for grp in mapSlodGrps where (grp.groupName != Undefined) do
|
|
(
|
|
local testName = grp.groupName + "|" + (grp.genLodLevel as String)
|
|
if (not AppendIfUnique usedGroupNames testName) do
|
|
(
|
|
local oldName = grp.ShowName()
|
|
|
|
-- Rename this lod-group
|
|
-- (this'll automatically pick a name with an unused index)
|
|
RsLodCombinerGroup.SetGrpNames #(grp) grp.groupName
|
|
|
|
local testName = grp.groupName + "|" + (grp.genLodLevel as String)
|
|
AppendIfUnique usedGroupNames testName
|
|
|
|
if (not renamed) do
|
|
Format "\nRenaming non-unique groups:\n"
|
|
renamed = True
|
|
Format " % => %\n " oldName (grp.ShowName())
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
popPrompt()
|
|
format "..[took %s]\n" ((timeStamp() - timeStart) / 1000.0)
|
|
|
|
return (not cancelled)
|
|
),
|
|
|
|
-- Remove objects from map's groups:
|
|
fn RemoveObjs remObjs removed:#{} checkAll:true removeEmpty:true updateBnds:True =
|
|
(
|
|
local remCount = removed.numberSet
|
|
|
|
for grpInfo in _groupList while checkAll or (removed.numberSet != remObjs.count) do
|
|
grpInfo.RemoveObjs remObjs removed:removed checkAll:checkAll removeEmpty:removeEmpty updateBnds:False
|
|
|
|
-- Reset bounds-data if any objects were removed from group or subgroups
|
|
if updateBnds and (removed.numberSet != remCount) do
|
|
this.UpdateBounds updateGrpBounds:True
|
|
|
|
return OK
|
|
),
|
|
|
|
-- Add new group(s) to map:
|
|
fn createGroups objLists doRemove:true parentData: grpName: =
|
|
(
|
|
-- Reconstitute groupList from dataNode if needed:
|
|
if (_groupList == undefined) do
|
|
this.Restore()
|
|
|
|
-- Get default group-name from map-struct:
|
|
if (grpName == unsupplied) do
|
|
(
|
|
grpName = groupName
|
|
)
|
|
|
|
local grpParentData = if (parentData == unsupplied) then This else parentData
|
|
|
|
local tempGrp = RsLodCombinerGroup()
|
|
tempGrp.CreateGroups objLists grpName:grpName grpParent:This doRemove:doRemove
|
|
)
|
|
)
|
|
|
|
-- Restart tool UI with new data-definitions, if open:
|
|
if (RsLodCombinerTool != undefined) and (RsLodCombinerTool.open) do
|
|
(
|
|
--RsLodCombinerTool.allowStore = False
|
|
RsLodCombinerTool.create()
|
|
)
|
|
|
|
if FALSE do
|
|
(
|
|
-- Tests restore-function on selected data-node:
|
|
local cancelled = false
|
|
(RsLodCombinerMap mapName:$.parent.name contNode:$.parent dataNode:$).restore cancelled:&cancelled
|
|
)
|