Files
ProjectEleri/BuildScripts/BakeWorldMap.bat

249 lines
19 KiB
Batchfile
Raw Permalink Normal View History

@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_<column>_<row>.bmp the whole map, one file a tile
REM WorldMap_Persistent_GameWorld.png the whole map, stitched
REM WorldMap_Persistent_GameWorld_Piece4x4_<c>_<r>-Tile_<c>_<r>.bmp one piece, one file a tile
REM WorldMap_Persistent_GameWorld_Piece4x4_<c>_<r>.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-<column>-<row>.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