Made map builder and exported first pass of maps
This commit is contained in:
217
Source/ProjectEleriEditor/Private/OrthoMap/WorldMapBuilder.h
Normal file
217
Source/ProjectEleriEditor/Private/OrthoMap/WorldMapBuilder.h
Normal file
@@ -0,0 +1,217 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WorldPartition/WorldPartitionBuilder.h"
|
||||
|
||||
#include "WorldMapBuilder.generated.h"
|
||||
|
||||
class AOrthoMapCaptureActor;
|
||||
class UTexture2D;
|
||||
|
||||
/** Which way round the world's two horizontal axes end up in the baked picture.
|
||||
*
|
||||
* None of this is worked out from the capture rotation. The builder asks the settings actor's own
|
||||
* WorldToBakedMapUV where a step along each world axis lands, because that mapping is what the game will use
|
||||
* to turn a world position into a map position, and a bake that arranged its tiles any other way would
|
||||
* disagree with it. A negative move means that axis is mirrored in the picture, which the stitch has to undo
|
||||
* while it copies the tile's own pixels in.
|
||||
*/
|
||||
struct FWorldMapOrientation
|
||||
{
|
||||
/** True when the picture's U runs along world X, false when it runs along world Y. */
|
||||
bool bUFollowsX = true;
|
||||
|
||||
/** True when the picture's V runs along world Y, false when it runs along world X. */
|
||||
bool bVFollowsY = true;
|
||||
|
||||
/** True when U grows towards smaller world X or Y, which is what the actor's bFlipU does to the mapping. */
|
||||
bool bMirrorU = false;
|
||||
|
||||
/** True when V grows towards smaller world Y or X, which is what the actor's bFlipV does to the mapping. */
|
||||
bool bMirrorV = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Bakes one world map texture for a world partition world, by capturing the region a tile at a time and
|
||||
* stitching the tiles back into a single asset.
|
||||
*
|
||||
* Run it as a commandlet:
|
||||
*
|
||||
* UnrealEditor-Cmd.exe <project> -run=WorldPartitionBuilderCommandlet
|
||||
* -Builder=/Script/ProjectEleriEditor.WorldMapBuilder -AllowCommandletRendering -SCCProvider=None
|
||||
* /Game/Maps/Persistent_GameWorld -log
|
||||
*
|
||||
* The region, the resolution and the flips all live on the AOrthoMapCaptureActor in the map rather than on this
|
||||
* builder, so that the picture that gets baked and the mapping the game reads map positions back through are
|
||||
* the same numbers read twice rather than two settings that have to be kept in step by hand.
|
||||
*/
|
||||
UCLASS(MinimalAPI)
|
||||
class UWorldMapBuilder : public UWorldPartitionBuilder
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Nothing can be captured without a renderer, and the engine insists a commandlet be told so. */
|
||||
virtual bool RequiresCommandletRendering() const override { return true; }
|
||||
|
||||
/** One capture per tile: the framework loads each tile's own region and unloads it again afterwards, which
|
||||
* is what keeps a map of a world partition world inside a sane memory budget rather than loading all of
|
||||
* it at once. */
|
||||
virtual ELoadingMode GetLoadingMode() const override { return ELoadingMode::IterativeCells2D; }
|
||||
|
||||
protected:
|
||||
virtual bool PreRun(UWorld* World, FPackageSourceControlHelper& PackageHelper) override;
|
||||
virtual bool RunInternal(UWorld* World, const FCellInfo& InCellInfo, FPackageSourceControlHelper& PackageHelper) override;
|
||||
virtual bool PostRun(UWorld* World, FPackageSourceControlHelper& PackageHelper, const bool bInRunSuccess) override;
|
||||
virtual bool ShouldSkipCell(const FWorldBuilderCellCoord& CellCoord) const override;
|
||||
|
||||
private:
|
||||
/** Finds the settings actor and works the grid out from it, including handing the framework the cell size
|
||||
* and bounds that make a cell index a tile index. Reports why not when the settings cannot be baked. */
|
||||
bool ReadSettings(UWorld* World, FText& OutReason);
|
||||
|
||||
/** Cuts the region the settings actor holds into the piece grid a piece run asks for, and points the actor at
|
||||
* the one piece this run is to bake. Reads -WorldMapPiece=<column>,<row> and -WorldMapPieceGrid=<n>, and
|
||||
* refuses anything about the pair, or about the region they are cut from, that would not give pieces tiling
|
||||
* that region exactly. Reports why not. */
|
||||
bool DerivePieceRegion(const FString& PieceIndexArgument, FText& OutReason);
|
||||
|
||||
/** Asks the mapping which way round the world axes land in the baked picture. Fails when both picture
|
||||
* directions turn out to follow the same world axis, which would put two tiles in one square of the map. */
|
||||
bool DeriveOrientation(const AOrthoMapCaptureActor& Settings);
|
||||
|
||||
/** The tile's position in the map's own grid of tiles: X is the tile index along the picture's U and Y the
|
||||
* index along its V, whichever world axes those turn out to be. */
|
||||
FIntPoint GetTileIndices(int32 CellX, int32 CellY) const;
|
||||
|
||||
/** Where a tile's pixels belong in the baked texture, flips and all. */
|
||||
FIntPoint GetTilePixelOrigin(const FIntPoint& TileIndices) const;
|
||||
|
||||
/** Checks that the tile's four world corners land inside this tile's own square of the map, which is what
|
||||
* says the grid and the mapping agree, before anything expensive is rendered. */
|
||||
bool CheckTileCoverage(const FBox& TileBounds, const FIntPoint& PixelOrigin) const;
|
||||
|
||||
/** The world region one tile covers, worked out from the grid rather than from the cell the framework
|
||||
* reported, so that what is captured can be checked against what the settings say before it is captured.
|
||||
* Z is the map's own Z range, which only sets the height the capture looks down from. */
|
||||
FBox GetTileWorldBounds(int64 CellX, int64 CellY) const;
|
||||
|
||||
/** Opens the package the baked map is saved into, creates the texture inside it or takes over the one a
|
||||
* previous bake left there, and locks that texture's only mip so the tiles have somewhere to be stitched
|
||||
* as they arrive. Until this has succeeded there is nothing to bake into. */
|
||||
bool OpenDestinationTexture(UWorld* World, FText& OutReason);
|
||||
|
||||
/** The texture every capture renders into, made on the first tile of the run and reused for the rest of it.
|
||||
* The capture helper re-initialises whatever texture it is handed, and a map's worth of dead sixteen
|
||||
* megabyte textures is a gigabyte the transient package cannot give back in the middle of a bake. */
|
||||
UTexture2D* GetTileTexture();
|
||||
|
||||
/** Copies one captured tile's pixels into the square of the map that tile owns, undoing whichever of the
|
||||
* two flips the settings asked for while it copies. */
|
||||
bool StitchTile(UTexture2D* InTileTexture, const FIntPoint& PixelOrigin);
|
||||
|
||||
/** Counts the pixels of a captured tile that have anything in them, which is how a tile that came back
|
||||
* empty is told apart from one that simply holds nothing but sky. */
|
||||
static int32 CountVisiblePixels(UTexture2D* TileTexture);
|
||||
|
||||
/** Writes a locked BGRA8 buffer out as an image under the project's intermediate files, which is what
|
||||
* -DebugCapture is for: a bitmap per tile, and a PNG for the whole stitched map, since sixteen thousand
|
||||
* pixels square is a gigabyte of bitmap and a few tens of megabytes of PNG. */
|
||||
void SaveDebugImage(const FString& InFileName, const uint8* Pixels, int32 Width, int32 Height, bool bAsPng) const;
|
||||
|
||||
/** Gives the baked texture the settings a world map asset needs, saves the package, and reports the file
|
||||
* to the framework. The last thing a successful bake does. */
|
||||
bool SaveBakedMap(FPackageSourceControlHelper& PackageHelper);
|
||||
|
||||
/** Puts the actor's own settings back after a run that overrode them. Harmless to call when nothing was
|
||||
* overridden. */
|
||||
void RestoreOverriddenSettings();
|
||||
|
||||
/** The actor the settings come from, kept referenced so that a collection while the builder runs cannot
|
||||
* leave this dangling. The numbers below are what a run actually uses. */
|
||||
UPROPERTY()
|
||||
TObjectPtr<AOrthoMapCaptureActor> SettingsActor;
|
||||
|
||||
FBox MapBounds = FBox(ForceInit);
|
||||
int64 GridOriginX = 0;
|
||||
int64 GridOriginY = 0;
|
||||
int32 TilesPerAxis = 1;
|
||||
int32 MapTextureSize = 0;
|
||||
int32 TilePixels = 0;
|
||||
double TileWorldSize = 0.0;
|
||||
FWorldMapOrientation Orientation;
|
||||
|
||||
/** Tiles walked so far and when the walk started, so that a run can say how it went at the end. */
|
||||
int32 WalkedTileCount = 0;
|
||||
double RunStartTime = 0.0;
|
||||
|
||||
/** The actor's own settings before any -MapTextureSize, -TilesPerAxis or region alignment override, and
|
||||
* whether they were changed. An override stays in place for the whole run, because the mapping the
|
||||
* coverage checks go through reads the actor's bounds, so putting them back happens in PostRun. */
|
||||
bool bOverrodeSettings = false;
|
||||
int32 OriginalMapTextureSize = 0;
|
||||
int32 OriginalTilesPerAxis = 0;
|
||||
FBox OriginalWorldMapBounds = FBox(ForceInit);
|
||||
|
||||
//----- A bake of one piece of the region -----
|
||||
|
||||
/** Whether this run bakes one piece of the region rather than the whole of it. A piece is a window onto the
|
||||
* region the actor holds, so its own box takes that region's place on the actor for the length of the run
|
||||
* and everything below reads it as the region without knowing the difference. */
|
||||
bool bPieceRun = false;
|
||||
|
||||
/** How many pieces a side the region is cut into, and which of them this run bakes. Four a side is the
|
||||
* default because that is how the in game map pages the island: the island stands where the middle four
|
||||
* pieces meet, and a UI that pages those four together shows all of it.
|
||||
*
|
||||
* The pieces are numbered by world axes rather than by the picture: the column runs along +X and the row
|
||||
* along +Y from the region's own min corner, so a piece's index says where it is in the world and does not
|
||||
* move when the map's own flips do. */
|
||||
int32 PieceColumn = 0;
|
||||
int32 PieceRow = 0;
|
||||
int32 PieceGrid = 4;
|
||||
|
||||
/** The region the pieces are cut out of. Kept because the piece's box takes that region's place on the actor
|
||||
* for the length of the run, and a log line that could not say what a piece is a piece of would be a log
|
||||
* line about a region nobody asked for. */
|
||||
FBox PieceParentBounds = FBox(ForceInit);
|
||||
|
||||
//----- The asset being baked -----
|
||||
|
||||
/** Name of the map the settings were read from, which is what the baked asset is named after. */
|
||||
FString MapName;
|
||||
|
||||
/** Where the baked map goes, held on the builder for the length of the run: the package, the texture
|
||||
* inside it, and the pixels of that texture's only mip. The mip is locked once for the whole walk rather
|
||||
* than once per tile, so the lock opens in PreRun and closes in PostRun whatever happens in between: a
|
||||
* lock left open would leave the texture unwritable for anyone who opened it next. */
|
||||
UPROPERTY()
|
||||
TObjectPtr<UPackage> DestinationPackage;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UTexture2D> DestinationTexture;
|
||||
|
||||
uint8* DestinationPixels = nullptr;
|
||||
FString DestinationAssetName;
|
||||
FString DestinationPackageName;
|
||||
FString DestinationPackageFileName;
|
||||
|
||||
/** The texture a capture renders into, held across the whole walk rather than made per tile. It is a
|
||||
* scratch buffer of one tile's pixels and nothing else: it is never saved, never registered, and lives in
|
||||
* the transient package. A plain pointer rather than a TObjectPtr because the capture helper takes the
|
||||
* texture by reference and may hand a different one back, which is a raw pointer the reference binds to and
|
||||
* a TObjectPtr is not. */
|
||||
UPROPERTY()
|
||||
UTexture2D* TileTexture = nullptr;
|
||||
|
||||
/** Whether -DebugCapture was given, in which case every tile and the stitched map are written out as
|
||||
* images as well. A bake costs a capture per tile either way; this only adds files to look at. */
|
||||
bool bDebugCapture = false;
|
||||
|
||||
/** Tiles that came back with nothing visible in them. A map is allowed to be mostly empty, but a tile
|
||||
* that rendered nothing at all is usually a setting rather than a stretch of landscape, and this count
|
||||
* is the one number that says which kind of bake this was. */
|
||||
int32 EmptyTileCount = 0;
|
||||
};
|
||||
Reference in New Issue
Block a user