@ECHO OFF REM Bakes the world map of the world partition map named at the end of each command below: the whole region as REM one asset, and the same region cut into a grid of pieces as one asset each. Seventeen textures come out of a REM full run, and the map UI in the game switches between them as it zooms: the whole region while the player is REM looking at all of it, and the piece they are looking at once they are close enough for the whole map's pixel REM to be a blur. REM REM The whole map is one texture over the whole region: on this map that is twelve thousand metres at 16384 REM pixels, which is 0.73 metres of world per pixel of picture, a map to open and see where one is. The pieces REM are the same region cut into a 4 x 4 grid, so three thousand metres a side, and each piece is baked at 8192 REM pixels, which is 0.37 metres of world per pixel: half the world per pixel of the whole map, which is the REM detail the zoomed in view is for. Both layers capture 2048 pixels of their own per tile, so a tile REM of a piece costs the same to render as a tile of the whole map and the difference between the layers is only REM how much world stands behind those pixels. REM REM The two layers are not a level of detail of each other, they are seventeen textures and the UI picks one, so REM they have to agree about where the world is to the pixel: they are baked from the same region by the same REM builder, and the piece a run bakes is the only thing about that the command lines below change. Nothing in REM the game reads one of the seventeen; they are pictures, and where the world is inside one of them is a REM question the settings actor answers separately. REM REM What the map covers is the WorldMapBounds of the OrthoMapCaptureActor in the map, which is the intended place REM to set it: open the map, select the actor, type the bounds in and save the map. A commandlet reads the map REM from disk, so a region that was never saved is not in what it reads. REM REM On this map the island stands on the origin and measures about 375 by 390 metres, centred within about 45 REM metres of it, so the region is the twelve kilometre square Min=(-600000,-600000,-100000) REM Max=(600000,600000,100000). The island is then about a thirtieth of the width of the picture and most of the REM rest is sea: ten of the whole map's sixty four tiles have anything in them, which is the island and what REM stands around it near the origin and a second group of tiles away to the west of it. What every run prints REM about its empty tiles is how that is checked. Z is not part of what the map covers, it is the height the REM captures look down from, and the Z in the actor's own box is where a run that passes no region gets one from. REM REM WorldMapBounds is also what the game reads when it turns a position on the map back into a world position, so REM the actor's numbers are the numbers this bake used, and the two cannot be allowed to drift apart. All six of REM them have to be on the actor before the run below, which passes no region of its own. REM REM Four flags exist for running without the editor. All apply to this run only and all override the actor: REM REM -WorldMapBounds=minX,minY,maxX,maxY states the region outright, keeping the actor's Z. It wins over the REM actor's own bounds, so it is the way to try a region out before REM committing it to the map. The whole map run below does not pass it: REM what gets baked is what the actor says. REM -FitBounds takes the region from the partition instead. Do not use it on this REM map. A world partition reports the bounds of its own sparse octree REM grid rather than the bounds of its content, and an actor in this REM map has a world sized bound of its own, which pushes the octree up REM to a level 30 cell either side of the origin. It answers with a box REM of +/-13743895347200 units, and a tile of that at eight tiles is REM 3435973836800 units across, far more than a cell can hold, so REM IsWorldMapGridValid refuses the bake and says so. The button of the REM same name on the actor's details panel has the same problem, so set REM the region by hand. REM -WorldMapPiece=column,row bakes one piece of the region instead of the whole of it. The REM column counts along +X from the region's Min end and the row counts REM along +Y from the same corner, so (0, 0) is the far corner of the REM map and the last piece is at the near one. REM -WorldMapPieceGrid=n says how many pieces a side that index is being read in: four for REM the 4 x 4 grid this script bakes. It has to be given with REM -WorldMapPiece, because the same index in another grid of the same REM region is a different square of the world, and it is only warned REM about when it arrives without one. REM REM A piece run refuses -WorldMapBounds and -FitBounds rather than combining them with a piece, because the two REM say different things about the same region: one states the region the pieces are cut out of, and the other REM states the piece itself. A region that has been stated can be cut into any grid at all, so there is no one REM right answer to what a piece of it means. Set the region on the actor first and then ask for a piece of it. REM A piece index outside the grid is refused too, and so is a piece of a region that is not a whole number of REM pieces across, which is a piece whose edges are not where the game thinks they are. REM REM -MapTextureSize and -TilesPerAxis shrink the map for a trial run: 512 pixels in 2 x 2 tiles walks the whole REM grid in four steps, and still covers the same region, because a region read off the actor is re-aligned to REM whatever grid the run actually walks. Given with -WorldMapPiece the same two shrink one piece instead, and REM that re-alignment is skipped: a piece's box is a window cut out of the region the pieces tile, so it is the REM tiles that follow the piece and not the other way round, and aligning a window to its own tile grid would REM quietly bake a region that is not one of the pieces. The whole map run below passes neither, so it bakes at REM the resolution and tile count set on the actor, which for this map are 16384 in eight tiles; the piece runs REM pass both, because 8192 in four tiles is what a piece is. REM REM The grid, and what the pieces are called. A piece goes beside the whole map in the same folder, named after it REM with the grid and the piece's own place on the end, so this map's seventeen assets are REM WorldMap_Persistent_GameWorld and WorldMap_Persistent_GameWorld_Piece4x4_0_0 through _Piece4x4_3_3. Column 0 REM is the end of the region at its Min and row 0 is the same corner, which puts this map's grid like this: REM REM row 3: (0,3) (1,3) (2,3) (3,3) every piece 3000 x 3000 metres REM row 2: (0,2) (1,2) (2,2) (3,2) the region Min=(-600000,-600000) Max=(600000,600000) REM row 1: (0,1) (1,1) (2,1) (3,1) +X runs along a row, +Y up a column REM row 0: (0,0) (1,0) (2,0) (3,0) the origin is the corner the four middle pieces share REM +--------------------------+ REM col 0 col 1 col 2 col 3 REM REM The island stands on the origin, so it falls across the four middle pieces (1,1) (2,1) (1,2) and (2,2), and REM Min=(-300000,-300000) Max=(300000,300000) covers those four and no others. The other twelve pieces are open REM sea: a bake of one of those that reports a tile with the island in it has the region wrong, and one of the REM four that reports nothing at all has it wrong the other way. REM REM Where the baked map goes is set on the actor too, by OutputPackageFolder, and the asset in it is named after REM the map it was baked from: this map bakes to /Game/Maps/Generated/WorldMap_Persistent_GameWorld, and baking REM it again replaces that same asset rather than making a second one. A piece is named the same way with its REM grid, column and row on the end, so the sixteen of them sit beside the whole map in the same folder and REM re-baking one piece is a change to one asset. That folder and WorldMapWarmupFrames are read off the actor as REM well and have to be set there, so a bake of a map whose actor has no output folder is refused before it REM renders anything. REM REM -DebugCapture also writes every tile it captures to Intermediate\WorldMap as a bitmap, and the stitched map as REM a PNG, and both are named after the asset the run is baking and after the tile's own place in the grid that REM run walked: REM REM WorldMap_Persistent_GameWorld-Tile__.bmp the whole map, one file a tile REM WorldMap_Persistent_GameWorld.png the whole map, stitched REM WorldMap_Persistent_GameWorld_Piece4x4__-Tile__.bmp one piece, one file a tile REM WorldMap_Persistent_GameWorld_Piece4x4__.png one piece, stitched REM REM Those two are the pair to look at when a bake comes out wrong: the tiles say whether each square captured REM what it should have, and the stitched map says whether they were placed in the right squares. The asset name REM in front of the tile index is what keeps the tiles of one piece from overwriting another's, which is in turn REM what makes the strip of tiles along a shared edge worth comparing: a seam between two pieces that is in the REM world reads as one line in each of them, and a seam the bake made reads as a difference. REM REM It is not on by default because it is a great deal of bitmap. A tile of this bake is 2048 pixels square at REM four bytes a pixel, which is sixteen megabytes, so the sixty four of the whole map are most of a gigabyte and REM the sixteen of one piece are a quarter of that. Add it to a command line below, or to the end of a manual REM run, when there is something to look at, and to one piece at a time when it is a seam being looked at. REM REM A full run of this script is seventeen commandlet runs: sixty four tiles for the whole map and sixteen for REM each of the sixteen pieces, which is three hundred and twenty tiles in all. On the machine this was written on REM all seventeen took eight and a half minutes, first log line to last. Almost none of that is capture: the whole REM map walks its sixty four tiles in seventeen seconds and a piece its sixteen in three to five, so the great REM majority of every run is the editor starting, reading the map and building the texture. Building is where the REM memory goes rather than the time: the whole map run peaks at eleven and a half gigabytes of physical memory, REM against the twelve gigabytes its sixteen thousand pixel texture reports needing while it is built, and a piece REM run peaks at five and a half against the three its eight thousand pixel one does. The runs are one after REM another rather than at once for the same reason a bake is one at a time: two of them at once would be two REM editors with a renderer each, holding two of these textures at the same time. REM REM What comes out is seventeen virtual textures, which is what lets a renderer hold a map of this size at all: a REM sixteen thousand pixel one that covers the whole region and sixteen eight thousand pixel ones that each cover a REM sixteenth of its area. They are small on disk because the region is all but empty. This map's own bake wrote the REM whole map as one and a half megabytes, the six pieces that have anything in them as between a third of a REM megabyte and two, and the ten that hold nothing but sea as fourteen kilobytes each, eight megabytes for the REM seventeen together. Where a map this size is large is on the card rather than on disk: what showing one needs is REM a virtual texture pool big enough for the part of it that is on screen, and this project's pool section gives it REM sixty four megabytes by default and, with bPoolAutoGrowInEditor off, does not grow while the editor runs. Sixty REM four megabytes is a great many of the pages these are cut into, and one of them on a screen is a small part of REM the pool; the entry to raise, if a UI ever shows more of them at once than the pool holds, is REM DefaultSizeInMegabyte in that section of Config\DefaultEngine.ini. REM REM The tiles that come back empty are counted out loud by every run, and that count is the quickest way to see a REM region that no longer covers the world: a region that has shrunk onto the world reports few or none of them REM empty, and one that has grown past the world reports all of them. On this map a whole map bake reports fifty REM four of its sixty four tiles empty, so ten of them have something in them, and the sixteen pieces report REM between ten and sixteen of their sixteen: ten of the pieces are all sea and come back entirely empty, the two REM west of the island report ten, the two the island's own corner is in report thirteen and the two east of those REM report fifteen. Those counts are of the world rather than of the bake, so they move when the map does; what is REM worth reading them for is a count that has gone to all empty or all full, which is the region and not the bake. REM -AllowCommandletRendering is not optional. A builder that captures is refused without it, because a REM commandlet has no renderer unless it is told to start one. REM REM The per corner coverage check is only as talkative as its own log category, which does not print its Verbose REM lines by default. A corner that lands outside its own tile is an Error and stops the walk, so a run that REM reaches the end of the grid has passed every corner of it. REM REM The editor commandlet has to be the same configuration the project modules were built in, or it will load REM modules that do not contain this builder. DebugGame is what a local build produces; uncomment the REM Development line to bake with a development build instead, which is the faster of the two. REM REM Every run below is logged to a file of its own under Intermediate\WorldMap as well as to the window: the whole REM map to whole-map.log and a piece to piece--.log, which is what makes a bake of seventeen runs REM readable after the fact. -abslog takes an absolute path, which is what %PROJECT_ROOT% below is for: the logs REM land in the project's own intermediate folder whatever directory this file is run from. It also moves that REM run's log out of the editor's Saved\Logs, and the file is written as the run goes rather than at its end, so REM one can be read, or tailed, while its bake is still walking the grid. SET EDITOR_CMD=C:/Program Files/Epic Games/UE_5.8/Engine/Binaries/Win64/UnrealEditor-Win64-DebugGame-Cmd.exe REM SET EDITOR_CMD=C:/Program Files/Epic Games/UE_5.8/Engine/Binaries/Win64/UnrealEditor-Cmd.exe SET PROJECT=C:/Projects/ProjectEleri/ProjectEleri.uproject REM The project's own folder, taken from the project file above so that this file can be run from anywhere: the REM per run logs and the trial run both hang off it. FOR %%I IN ("%PROJECT%") DO SET PROJECT_ROOT=%%~dpI REM What a run of this file bakes. The whole map is baked at whatever resolution and tile count the settings actor REM carries, which for this map are 16384 in eight tiles; a piece is baked at the two numbers below, because 8192 REM pixels over a quarter of the width is what a piece is. SET MAP=/Game/Maps/Persistent_GameWorld SET BUILDER=-run=WorldPartitionBuilderCommandlet -Builder=/Script/ProjectEleriEditor.WorldMapBuilder -AllowCommandletRendering -SCCProvider=None -unattended -Verbose SET PIECE_SIZE=4096 SET PIECE_TILES=4 SET PIECE_GRID=8 SET PIECE_ROOT=%PROJECT_ROOT%Intermediate\WorldMap REM Whether to bake the whole map as well as the pieces. Set to 0 to bake only the sixteen pieces, which is what REM a run that changed the piece numbers and nothing else wants. SET WHOLE_MAP=1 REM Bake one piece instead of all sixteen: its column and row as -WorldMapPiece takes them, or empty for all of REM them. ONLY_PIECE=1,1 is the piece the island's own corner is in, and is the one to bake when the piece layer REM is being tried out rather than built. SET ONLY_PIECE= IF NOT EXIST "%PIECE_ROOT%" MKDIR "%PIECE_ROOT%" REM The geometry trial of one piece: four tiles of 512 pixels over the whole square a real piece covers, with a REM bitmap of each to look at. It bakes one asset rather than seventeen and its log says which region that was, REM so it is the cheap first thing to run when the piece arithmetic in the builder changes. REM "%EDITOR_CMD%" "%PROJECT%" %BUILDER% %MAP% -WorldMapPiece=0,0 -WorldMapPieceGrid=%PIECE_GRID% -MapTextureSize=512 -TilesPerAxis=2 -DebugCapture -log -stdout IF "%WHOLE_MAP%"=="1" ( ECHO "Baking the whole map..." "%EDITOR_CMD%" "%PROJECT%" %BUILDER% %MAP% -abslog="%PIECE_ROOT%\whole-map.log" -log -stdout IF ERRORLEVEL 1 GOTO :BAKE_FAILED ) ECHO "Baking the sixteen pieces..." SET /A PIECE_LAST=%PIECE_GRID% - 1 FOR /L %%R IN (0,1,%PIECE_LAST%) DO ( FOR /L %%C IN (0,1,%PIECE_LAST%) DO ( CALL :bake_piece %%C %%R IF ERRORLEVEL 1 GOTO :BAKE_FAILED ) ) ECHO "DONE! The whole map and the sixteen pieces of it are in the output folder set on the settings actor." PAUSE EXIT /B 0 :BAKE_FAILED ECHO "A run failed, so the pieces after it were not baked. The run that failed says why in the log above." ECHO "Fix it and run this file again: the runs that succeeded are saved already, and both the failed run and the" ECHO "skipped ones replace what is there rather than making second assets. To bake one of them on its own, set" ECHO "ONLY_PIECE at the top of this file to its column and row and set WHOLE_MAP to 0." PAUSE EXIT /B 1 REM One piece: the region comes from the actor and the piece from the two arguments, which are its column and its REM row. A piece that is not the one ONLY_PIECE asks for is skipped without a word, which is what makes that REM setting a way to bake one piece through the same loop as all sixteen. :bake_piece IF NOT "%ONLY_PIECE%"=="" IF NOT "%ONLY_PIECE%"=="%1,%2" EXIT /B 0 ECHO "Baking piece %1,%2..." "%EDITOR_CMD%" "%PROJECT%" %BUILDER% %MAP% -WorldMapPiece=%1,%2 -WorldMapPieceGrid=%PIECE_GRID% -MapTextureSize=%PIECE_SIZE% -TilesPerAxis=%PIECE_TILES% -abslog="%PIECE_ROOT%\piece-%1-%2.log" -log -stdout IF ERRORLEVEL 1 EXIT /B 1 EXIT /B 0