filein (RsConfigGetWildWestDir() + "script/3dsMax/_config_files/Wildwest_header.ms") struct proceduralMapperStruct ( tileSize, worldMapSize = [9150, 12450], worldBottomCorner = [-4000, -4950], worldOffset = [4000, 4950], tileSize = 50.0, tileCount = [(worldMapSize.x / tileSize), (worldMapSize.y / tileSize)], maxTileIdx = (worldMapSize.x / tileSize) as Integer, maxTileIdy = (worldMapSize.y / tileSize) as Integer, containerPositions = #(), containerBounds, windowsTEMPDir = systemTools.getEnvVariable "TEMP", mapTileArchivePath = (RsConfigGetAssetsDir() + "reports/heightmap/images_4_5/materialmasks.zip"), extractPath = windowsTEMPDir + "/ProcMasks", maskPaths = extractPath + "/materialmasks/", tempMaskPath = windowsTEMPDir + "/TempMaterialMasks/", tempProcPaintPath = windowsTEMPDir + "/ProcPaint/", procPaintPath = (RsConfigGetAssetsDir() + "maps/procPaint/"), borderDist = 4, --border tiles around a bounds selection cellBounds, containerTiles, --tileRect, UIEditLayers = #(), UIMaskLayers = #(), editLayers = #((tempProcPaintPath + "Procedural Sets")), maskLayers = #(), editTypes = #("Procedural_Sets"), procMaskImages = #(), collProcMaskImages = #(), maskTypes = #(), editProcessList = #("ProceduralSetMap"), maskProcessList = #(), procedural_meta = (RsConfigGetCommonDir() + "data/materials/procedural.meta"), procedural_metaTypes = #(), containerProcTypes = #(), rolloutUI = undefined, --///////////////////////////////////////// -- Send a message to the status UI --///////////////////////////////////////// fn updateUIStatus msg = ( if rolloutUI != undefined then ( rolloutUI.edtStatus.text = msg ) ), --///////////////////////////////////////// -- Set progress bar status UI --///////////////////////////////////////// fn setProgress val = ( if rolloutUI != undefined then ( rolloutUI.prgBar.value = val ) ), --///////////////////////////////////////// -- Set the options for saving png images in the application --///////////////////////////////////////// fn setPNGOpts type = ( case type of ( #editLayer:portable_network_graphics.setType #true24 #maskLayer:portable_network_graphics.setType #true24 ) portable_network_graphics.setAlpha false portable_network_graphics.setInterlaced false ), --///////////////////////////////////////// -- --///////////////////////////////////////// fn getTileByPos pos = ( local offsetX = worldOffset.x + pos.x if offsetX > worldMapSize.x then format "Out of map bounds X: %\n" offsetX local offsetY = worldOffset.y + pos.y if offsetY > worldMapSize.y then format "Out of map bounds Y: %\n" offsetY local x = (offsetX - (mod offsetX tileSize)) / tileSize local y = (offsetY - (mod offsetY tileSize)) / tileSize format "tile from pos:% \n" [x, y] return [x, y] ), --///////////////////////////////////////// --tile is a Point2 --dir is a Point2 --///////////////////////////////////////// fn getTileByAdjacency tile dir = ( [(tile.x + dir.x), (tile.y + dir.y)] ), --///////////////////////////////////////// -- --///////////////////////////////////////// fn getBorderTiles tile border = ( tileBorder = #() for x = -border to border do ( for y = -border to border do ( local newX = tile.x + x local newY = tile.y + y if (x != 0 or y != 0) and (newX > -1 and newX < maxTileIdx and newY > -1 and newY < maxTileIdy) then ( append tileBorder [newX, newY] ) ) ) --return tileBorder ), --///////////////////////////////////////// -- --///////////////////////////////////////// fn tileBounds tileArray = ( bounds = undefined local xRange = for item in tileArray collect item.x local yRange = for item in tileArray collect item.y local minX = amin xRange as Integer local maxX = amax xRange as Integer local minY = amin yRange as Integer local maxY = amax yRange as Integer --return bounds = #([minX, minY], [maxX, maxY]) ), --///////////////////////////////////////// -- --///////////////////////////////////////// fn getTilesByBounds = ( if containerBounds == undefined then return false --using the bounds find all the tiles it covers and return a tileRect local minBoundsTile = getTileByPos containerBounds[1] local maxBoundsTile = getTileByPos containerBounds[2] containerTiles = tileBounds #(minBoundsTile, maxBoundsTile) --expand the selection to all the tiles within the bounds ), --///////////////////////////////////////// -- --///////////////////////////////////////// fn expandBounds minBB maxBB currentBounds = ( local minBounds = currentBounds[1] local maxBounds = currentBounds[2] if ( minBB.x < minBounds.x ) then ( minBounds.x = minBB.x ) if ( minBB.y < minBounds.y ) then ( minBounds.y = minBB.y ) if ( maxBB.x > maxBounds.x ) then ( maxBounds.x = maxBB.x ) if ( maxBB.y > maxBounds.y ) then ( maxBounds.y = maxBB.y ) #(minBounds, maxBounds) ), --///////////////////////////////////////// -- For all the containers in the scene determine the bounds that cover all --///////////////////////////////////////// fn getAggregateContainerBounds = ( local conts = RsMapGetMapContainers() if conts.count == 0 then ( messagebox "No containers loaded to work with." title:"ERROR" return false ) containerBounds = nodeGetBoundingBox conts[1].cont (matrix3 1) for c in conts do ( local thisContainer = c.cont local bounds = nodeGetBoundingBox thisContainer (matrix3 1) containerBounds = expandBounds bounds[1] bounds[2] containerBounds ) ), --///////////////////////////////////////// -- Check the bounds do not go out of range and trim them if they do --///////////////////////////////////////// fn checkTileBounds = ( local minBounds = containerTiles[1] local maxBounds = containerTiles[2] --check and trim bounds --min if minBounds.x < 0 then containerTiles[1].x = 0 if minBounds.x > tileCount.x then containerTiles[1].x = tileCount.x if minBounds.y < 0 then containerTiles[1].y = 0 if minBounds.y > tileCount.y then containerTiles[1].y = tileCount.y --max if maxBounds.x < 0 then containerTiles[2].x = 0 if maxBounds.x > tileCount.x then containerTiles[2].x = tileCount.x if maxBounds.y < 0 then containerTiles[2].y = 0 if maxBounds.y > tileCount.y then containerTiles[2].y = tileCount.y --check min < max if containerTiles[2].x < containerTiles[1].x then containerTiles[2].x = containerTiles[1].x + 1 if containerTiles[2].y < containerTiles[1].y then containerTiles[2].y = containerTiles[1].y + 1 --check for degenerate bounds if containerTiles[1].x == containerTiles[2].x then containerTiles[2].x += 1 if containerTiles[1].y == containerTiles[2].y then containerTiles[2].y += 1 ok ), --///////////////////////////////////////// -- --///////////////////////////////////////// fn getContainerPositions = ( local conts = RsMapGetMapContainers() containerPositions = for item in conts collect item.cont.pos if containerPositions.count == 0 then containerPositions = #([0,0,0]) ), --///////////////////////////////////////// -- get the tile maps surrounfing a given tile and merge the images into -- one combined map for editing --///////////////////////////////////////// fn combineTileMaps tile = ( local cellTiles = getBorderTiles tile borderDist ), --///////////////////////////////////////// -- Extract the materialmask archive --///////////////////////////////////////// fn extractMaskMaps = ( --Clean the old ones out first for filename in (getFiles (extractPath + "*.png")) do deleteFile filename --check for newest version local fstat = ( gRsPerforce.getFileStats mapTileArchivePath )[ 1 ] local haveRev = fstat.Item[ "haveRev" ] local headRev = fstat.Item[ "headRev" ] local doUpDateMasks = false if (haveRev == undefined) or ((haveRev as Integer) < (headRev as Integer)) then ( print "Syncing to head revision" gRsPerforce.sync mapTileArchivePath doUpDateMasks = true ) --check we have the archive if (doesFileExist mapTileArchivePath ) or doUpDateMasks then ( --extract to the temp dir if (getDirectories extractPath).count == 0 then makeDir (RsMakeSafeSlashes(extractPath)) zipFile = (dotNetClass "Ionic.Zip.ZipFile").Read mapTileArchivePath zipFile.TempFileFolder = extractPath zipFile.ExtractSelectedEntries "name = *.png" "materialmasks" extractPath (dotNetClass "Ionic.Zip.ExtractExistingFileAction").OverwriteSilently --zipFile.ExtractAll extractPath (dotNetClass "Ionic.Zip.ExtractExistingFileAction").OverwriteSilently --clean zipFile.dispose() ) OK ), --///////////////////////////////////////// -- using a tile index with bottom left being 0,0 -- return the top left corner in bitmap coords -- bmSize is the size of the bitmap - [x, y] -- tileSize is the size of a tile in pixels -- tile is the tile index of interest -- return a Point2 coord [x, y] --///////////////////////////////////////// fn getTileBitmapOrigin bmSize tileSize tileRect = ( local pixelTileSize = bmSize / tileCount --tileRect is #([x,y], [x,y]) local imageX = tileRect[1].x * pixelTileSize.x local imageY = tileRect[1].y * pixelTileSize.y --return format "image xy:% \n" [imageX, imageY] return [imageX, imageY] ), --///////////////////////////////////////// -- get the size in pixels of the tile Rectangle -- unsing the supplied bitmap dimensions --///////////////////////////////////////// fn getTileBitmapDimensions bmSize tileRect = ( local pixelTileSize = bmSize / tileCount (pixelTileSize * [(tileRect[2].x - tileRect[1].x), (tileRect[2].y - tileRect[1].y)]) ), --///////////////////////////////////////// -- --///////////////////////////////////////// fn getTileFilePath tile = ( local tilePath = extractPath + "materialmasks/" ), --///////////////////////////////////////// -- extract a section from an image -- and paste it into a new bitmap object to be returned --///////////////////////////////////////// fn copyBitmapRegion imagePath tileRect = ( if (not (doesFileExist imagePath)) then return undefined local sourceBMP = openBitmap imagePath local tilePixelsOrigin = getTileBitmapOrigin [sourceBMP.Width, sourceBMP.Height] tileSize tileRect local tilePixelsDim = getTileBitmapDimensions [sourceBMP.Width, sourceBMP.Height] tileRect local compBm = bitmap tilePixelsDim.x tilePixelsDim.y color:black pasteBitmap sourceBMP compBm (box2 tilePixelsOrigin.x tilePixelsOrigin.y tilePixelsDim.x tilePixelsDim.y) [0, 0] close sourceBMP free sourceBMP --debug --display compBm --return compBm ), --///////////////////////////////////////// -- --///////////////////////////////////////// fn prepareMap type bmpName = ( case type of ( #editLayer: ( local topLeftTileName = "_" + (containerTiles[1].x as Integer) as String + "_" + (containerTiles[1].y as Integer) as String local bmpPath = procPaintPath + bmpName + ".png" if (not (doesFileExist bmpPath)) then --try to sync it ( gRsPerforce.sync #(bmpPath) ) if (not (doesFileExist bmpPath)) then --missing ( messageBox ("Cant find file:" + bmpPath) title:"Missing File" return false ) local regionBmp = copyBitmapRegion bmpPath containerTiles local outPath = tempProcPaintPath + bmpName + topLeftTileName + ".png" regionBmp.filename = outPath setPNGOpts #editLayer save regionBmp free regionBmp append editLayers outPath ) #maskLayer: ( local bmpPath = maskPaths + bmpName + ".png" local regionBmp = copyBitmapRegion bmpPath containerTiles local outPath = tempProcPaintPath + bmpName + ".png" regionBmp.filename = outPath setPNGOpts #maskLayer save regionBmp free regionBmp append maskLayers outPath ) ) ), --///////////////////////////////////////// -- --///////////////////////////////////////// fn constructMaps = ( updateUIStatus "Constructing working images" --check dirs exist, create if need be for dir in #(extractPath, maskPaths, tempMaskPath, tempProcPaintPath) where (not (doesFileExist dir)) do ( makeDir dir ) --check if file exists already, prompt for update for filename in (getFiles (tempProcPaintPath + "*.png")) do deleteFile filename --process maps updateUIStatus "Processing masks..." setProgress 0 local prgMax = maskProcessList.count for i=1 to maskProcessList.count do ( local thisItem = maskProcessList[i] local bmpName = getFilenameFile thisItem prepareMap #maskLayer bmpName setProgress (100.0 * ( i as float / prgMax)) ) setProgress 0 updateUIStatus "Processing edit maps..." prgMax = editProcessList.count for i=1 to editProcessList.count do ( local thisItem = editProcessList[i] local bmpName = getFilenameFile thisItem prepareMap #editLayer bmpName setProgress (100.0 * ( i as float / prgMax)) ) setProgress 0 updateUIStatus "." ), --///////////////////////////////////////// -- Find all the extracted mask images --///////////////////////////////////////// fn discoverProceduralMaskImages = ( local files = getFiles (maskPaths + "*.png") if files.count == 0 then return false -- 'ALL_' type procedural images procMaskImages = getFiles (maskPaths + "ALL_*.png") -- collision/procedural combinations collProcMaskImages = for name in files where (MatchPattern (getFilenameFile name) pattern:"ALL_*") == false collect name OK ), --///////////////////////////////////////// --Break up the collProcMaskImages list into lists by category --///////////////////////////////////////// fn categoriseMasks = ( ---------------------------------- -- procMaskImages ---------------------------------- if procMaskImages.count != 0 then ( --determine types local scratchMaskTypes = #() for item in procMaskImages do ( local nameBits = filterString (getFileNameFile item) "_" if nameBits.count > 0 then appendIfUnique scratchMaskTypes nameBits[1] ) --now create lists of mask images under these types for type in scratchMaskTypes do ( local maskList = for item in procMaskImages where MatchPattern (getFilenameFile item) pattern:(type + "*") collect item append maskTypes (DataPair type:type masks:maskList) ) ) ---------------------------------- -- collProcMaskImages ---------------------------------- if collProcMaskImages.count != 0 then ( --determine types local scratchMaskTypes = #() for item in collProcMaskImages do ( local nameBits = filterString (getFileNameFile item) "_" if nameBits.count > 0 then appendIfUnique scratchMaskTypes nameBits[1] ) --now create lists of mask images under these types for type in scratchMaskTypes do ( local maskList = for item in collProcMaskImages where MatchPattern (getFilenameFile item) pattern:(type + "*") collect item append maskTypes (DataPair type:type masks:maskList) ) ) OK ), --///////////////////////////////////////// -- --///////////////////////////////////////// fn processScene = ( --get the containers positions --getContainerPositions() --get the containers bounds getAggregateContainerBounds() --find the tiles --containerTiles = for cPos in containerPositions collect (getTileByPos cPos) getTilesByBounds() --grow the border local minBorder = [(containerTiles[1].x - borderDist), (containerTiles[1].y - borderDist)] local maxBorder = [(containerTiles[2].x + borderDist), (containerTiles[2].y + borderDist)] containerTiles = expandBounds minBorder maxBorder containerTiles --trim the bounds if they go out of range checkTileBounds() --get the bounds of the tiles to make a rectangular area --tileRect = tileBounds containerTiles format "containerBounds: % \n" containerBounds --extract the map archive to build the composite image from print "extractMaskMaps" discoverProceduralMaskImages() --extractMaskMaps() --build a composite image for each mask and the edit layer print "ConstructMaps" constructMaps() --send to photoshop for editing ), --////////////////////////////////////////////////////////////////////// -- Get all the types defined in procedural.meta --////////////////////////////////////////////////////////////////////// fn parseProcedural_meta = ( local xmlDoc = XmlDocument() xmlDoc.load procedural_meta local procTags = xmlDoc.document.selectNodes "./CProceduralInfo/procObjInfos/Item/Tag" procedural_metaTypes = #() for i=0 to (procTags.count - 1) do ( appendIfUnique procedural_metaTypes procTags.itemOf[i].InnerText ) sort procedural_metaTypes ), --///////////////////////////////////////// -- --///////////////////////////////////////// fn discoverProcedural_metaTypes = ( for item in collProcMaskImages do ( stringBits = filterString (getFilenameFile item) "[*]" if (stringBits[2] != undefined) then ( appendIfUnique procedural_metaTypes stringBits[2] ) ) sort procedural_metaTypes ), --///////////////////////////////////////// -- Analyse all the collision meshes -- in the scene and collect all the proctypes from the assigned materials -- Then tag the tree mask items that match against those. --///////////////////////////////////////// fn collectSceneProcTypes = ( for obj in objects where (isKindOf obj col_mesh) do ( --get the applied material IDs --copyMesh = copy obj local objMesh = getColMesh obj local materialIDs = for face in objMesh.faces collect (getFaceMatID objMesh face.index) materialIDs = sort(makeUniqueArray materialIDs) for id in materialIDs do ( local procType = RexGetProceduralName obj.material.materialList[id] if procType != "" then ( append containerProcTypes procType ) ) --clean up --delete copyMesh --delete objMesh ) containerProcTypes = makeUniqueArray containerProcTypes ), --///////////////////////////////////////// -- --///////////////////////////////////////// on create do ( --discover if we have the latest materialmasks zip extractMaskMaps() discoverProceduralMaskImages() categoriseMasks() discoverProcedural_metaTypes() collectSceneProcTypes() --parseProcedural_meta() ) ) --///////////////////////////////////////// -- UI --///////////////////////////////////////// rollout InitMessageUI "Initialising.." width:200 height:60 ( label lblInitMsg "Initialising Procedural Mapper" align:#left label lblWait "Please Wait..." align:#left ) createDialog InitMessageUI try(destroyDialog ProceduralMapperUI)catch() rollout ProceduralMapperUI "Procedural Mapper" width:400 height:725 ( --///////////////////////////////////////// -- VARIABLES --///////////////////////////////////////// local procMapper = proceduralMapperStruct() local nodeActionList = #() --///////////////////////////////////////// -- CONTROLS --///////////////////////////////////////// dotNetControl rsBannerPanel "Panel" pos:[0,0] height:32 width:400 local banner = makeRsBanner dn_Panel:rsBannerPanel wiki:"ProceduralMapper" filename:(getThisScriptFilename()) dropdownlist ddlProcTypes items:procMapper.procedural_metaTypes width:(ProceduralMapperUI.width - 110) across:2 button btnSelectMasks "Select Masks" align:#right dotNetControl dnTreeView "System.Windows.Forms.TreeView" width:(ProceduralMapperUI.width - 25) height:(ProceduralMapperUI.height - 145) button btnEditMaps "Edit Maps" width:(ProceduralMapperUI.width - 25) progressBar prgBar editText edtStatus text:"Status" enabled:false --///////////////////////////////////////// -- FUNCTIONS --///////////////////////////////////////// --///////////////////////////////////////// -- Populates the tree control with content --///////////////////////////////////////// fn buildTree = ( --editLayers editLayersNode = dnTreeView.nodes.Add "Edit Layers" editLayersNode.checked = true --add the child nodes for item in procMapper.editTypes do ( local newNode = editLayersNode.nodes.add item newNode.tag = item newNode.checked = true ) --maskLayers maskLayersNode = dnTreeView.nodes.Add "Mask Layers" maskLayersNode.expand() --add the child nodes --first by category local maskTypeNodes = #() for item in procMapper.maskTypes do ( append maskTypeNodes (maskLayersNode.nodes.add item.type) ) --then the children for each category for i = 1 to maskTypeNodes.count do ( for childMask in procMapper.maskTypes[i].masks do ( local aNode = maskTypeNodes[i].nodes.add (getFileNameFile childMask) aNode.tag = childMask --tick it on if it matches to a proctype in the scene local childMaskProcType = FilterString childMask "[]" if childMaskProcType.count > 1 then ( if (findItem procMapper.containerProcTypes childMaskProcType[2]) != 0 then ( aNode.checked = true local root = false local parentNode = aNode.parent while (parentNode != undefined) do ( parentNode.Expand() parentNode = parentNode.parent ) ) ) ) ) ) --///////////////////////////////////////// -- setup the dotnet UI --///////////////////////////////////////// fn init = ( procMapper.rolloutUI = ProceduralMapperUI dnTreeView.CheckBoxes = true buildTree() ) --///////////////////////////////////////// -- Traverse up the tree to the root --///////////////////////////////////////// fn findRoot node = ( if node.parent != undefined then ( findRoot node.parent ) else ( return node ) ) --///////////////////////////////////////// -- update the nodeActionList -- when a node is check or not --///////////////////////////////////////// fn updateMaskProcessList item state = ( --add checked nodes to the list for processing local treeRoot = findRoot item if treeRoot.text == "Mask Layers" then ( if state == true then ( appendIfUnique procMapper.maskProcessList item.tag ) else --remove them ( --print "remove" local id = findItem procMapper.maskProcessList item.tag if id != 0 then deleteItem procMapper.maskProcessList id ) ) ) --///////////////////////////////////////// -- Get the root mask treeview nodes list --///////////////////////////////////////// fn getMaskLayerRootNodes = ( local maskLayersNodeChildren = #() local maskLayersNode = for t=0 to (dnTreeView.nodes.count - 1) \ where (MatchPattern dnTreeView.nodes.item[t].text pattern:"Mask Layers" ) \ collect dnTreeView.nodes.item[t] if maskLayersNode.count != 0 then ( maskLayersNodeChildren = for t=0 to (maskLayersNode[1].nodes.count - 1) collect maskLayersNode[1].nodes.item[t] ) --retval maskLayersNodeChildren ) --////////////////////////////////////////////////////////////////////////////////////// --switch all treeNode child nodes to checkbox state --////////////////////////////////////////////////////////////////////////////////////// fn checkTreeNodeChildren treeNodes state = ( for thisNode in treeNodes do ( --thisNode = treeNodes.item[n] thisNode.checked = state --add checked nodes to the list for processing updateMaskProcessList thisNode state --recurse? if thisNode.nodes.count > 0 then ( newNodes = for t=0 to (thisNode.nodes.count - 1) collect thisNode.nodes.item[t] checkTreeNodeChildren newNodes state ) ) ) --///////////////////////////////////////// -- --///////////////////////////////////////// fn findMatchingNode maskName treeNodes &nodes = ( local found = False for thisNode in treeNodes while not found do ( --format "node: % mask: % \n" (tolower thisNode.text) (tolower maskName) if (tolower thisNode.text) == (tolower maskName) then ( found = True append nodes thisNode ) --recurse? if thisNode.nodes.count > 0 then ( newNodes = for t=0 to (thisNode.nodes.count - 1) collect thisNode.nodes.item[t] findMatchingNode maskName newNodes &nodes ) ) ) --///////////////////////////////////////// -- Search for nodes matching names and Switch --///////////////////////////////////////// fn checkMatchingNodes namesList = ( for maskName in namesList do ( local nodes = #() findMatchingNode (getFilenameFile maskName) (getMaskLayerRootNodes()) &nodes --print nodes --set node to checked if nodes != undefined then ( for node in nodes do node.checked = True ) ) ) --///////////////////////////////////////// -- EVENTS --///////////////////////////////////////// --///////////////////////////////////////// -- Select any mask items in the list -- matching the currently selected Procedural from the dropdown --///////////////////////////////////////// on btnSelectMasks pressed do ( local procFromList = tolower ddlProcTypes.selected --local masksArray = for item in procMapper.maskTypes collect item.masks local matchingMasks = #() for item in procMapper.collProcMaskImages do ( if MatchPattern (getFilenameFile item) pattern:("*["+procFromList+"]*") ignoreCase:true then append matchingMasks item ) if matchingMasks.count != 0 then ( --clear any checked selection first checkTreeNodeChildren (for t=0 to (dnTreeView.nodes.count - 1) collect dnTreeView.nodes.item[t]) False --take the matches and set the tree nodes checkMatchingNodes matchingMasks ) ) --///////////////////////////////////////// -- --///////////////////////////////////////// on dnTreeView AfterCheck sender ev do ( --print "check" --print ev --print sender if ev.node.nodes.count > 0 then ( childNodes = for n=0 to (ev.node.nodes.count - 1) collect ev.node.nodes.item[n] checkTreeNodeChildren childNodes ev.node.checked ) if ev.node.nodes.count == 0 then ( updateMaskProcessList ev.node ev.node.checked ) ) --///////////////////////////////////////// -- --///////////////////////////////////////// on btnEditMaps pressed do ( --From the checked tree nodes build a list of mask maps to process for working with --Now process them procMapper.processScene() ) --///////////////////////////////////////// -- --///////////////////////////////////////// on ProceduralMapperUI open do ( banner.setup() init() try(DestroyDialog InitMessageUI)catch() ) ) createDialog ProceduralMapperUI