// Copyright Epic Games, Inc. All Rights Reserved. #include "LandscapeTiledImage.h" #include "HAL/FileManager.h" #include "Internationalization/Regex.h" #include "LandscapeImageFileCache.h" #include "Internationalization/Internationalization.h" #define LOCTEXT_NAMESPACE "LandscapeEditor" TMap FLandscapeTiledImage::Tokens = { { TEXT("u"), TEXT("") }, { TEXT("v"), TEXT("") }, { TEXT("x"), TEXT("") }, { TEXT("y"), TEXT("") } }; FString FLandscapeTiledImage::GetTokenRegex(const FString& Prefix) { return Prefix + TEXT("(-?[0-9]+)"); } FLandscapeTiledImage::FLandscapeTiledImage() { } template FLandscapeFileInfo FLandscapeTiledImage::Load(const TCHAR* Filename) { FLandscapeFileInfo Result; TArray FoundFiles; FindFiles(Filename, FoundFiles); FString Path = FPaths::GetPath(Filename); FString FilenameNoPath = FPaths::GetCleanFilename(Filename); FString RegexFilename(FilenameNoPath); TMap Groups; for (const TPair& It : Tokens) { if (int32 StartIndex = RegexFilename.Find(It.Value); StartIndex != INDEX_NONE) { Groups.Add(StartIndex, It.Value); } } Groups.KeySort([](int32 A, int32 B) { return A < B; }); for (const TPair& It : Tokens) { if (int32 start = RegexFilename.Find(It.Value); start != INDEX_NONE) { RegexFilename.RemoveAt(start, It.Value.Len()); RegexFilename.InsertAt(start, GetTokenRegex("")); } } const bool bExactFilename = RegexFilename == FilenameNoPath; SizeInTiles = FIntPoint::NoneValue; TileResolution = FIntPoint::NoneValue; if (FoundFiles.Num() == 1 && bExactFilename) { FIntPoint TileCoord(0, 0); TileFilenames.Add(TileCoord, FString(Filename)); } else { for (const FString& FoundFilname : FoundFiles) { FRegexPattern Pattern(RegexFilename); // Regex test the filename without the path so that conflicting symbols (e.g. brackets) in the path don't confuse it. FRegexMatcher Matcher(Pattern, FoundFilname); if (Matcher.FindNext()) { int32 X = -1; int32 Y = -1; int32 Group = 1; for (const TPair &it : Groups) { if (it.Value == "") { X = FCString::Atoi(*Matcher.GetCaptureGroup(Group++)) - 1; } else if (it.Value == "") { X = FCString::Atoi(*Matcher.GetCaptureGroup(Group++)); } else if (it.Value == "") { Y = FCString::Atoi(*Matcher.GetCaptureGroup(Group++)) - 1; } else if (it.Value == "") { Y = FCString::Atoi(*Matcher.GetCaptureGroup(Group++)); } } if (X >= 0 && Y >= 0) { FIntPoint TileCoord(X, Y); FString FullPath = FPaths::Combine(Path, FoundFilname); TileFilenames.Add(TileCoord, FullPath); } } } } for (const TPair & Tile : TileFilenames) { FLandscapeImageDataRef ImageData; FLandscapeImageFileCache& LandscapeImageFileCache = FModuleManager::GetModuleChecked("LandscapeEditor").GetImageFileCache(); FLandscapeFileInfo TileResult = LandscapeImageFileCache.FindImage(*Tile.Value, ImageData); if (TileResult.ResultCode == ELandscapeImportResult::Error) { return TileResult; } else if (TileResult.ResultCode == ELandscapeImportResult::Warning) { Result.ResultCode = TileResult.ResultCode; Result.ErrorMessage = TileResult.ErrorMessage; } SizeInTiles.X = FMath::Max(SizeInTiles.X, Tile.Key.X + 1); SizeInTiles.Y = FMath::Max(SizeInTiles.Y, Tile.Key.Y + 1); uint32 Width = ImageData.Resolution.X; uint32 Height = ImageData.Resolution.Y; if (TileResolution == FIntPoint::NoneValue) { TileResolution.X = Width; TileResolution.Y = Height; } else if (Width != TileResolution.X && Height != TileResolution.Y) { Result.ResultCode = ELandscapeImportResult::Error; Result.ErrorMessage = LOCTEXT("FileReadErrorTiledResolutionMismatch", "Mismatched resolution found in tiled image"); return Result; } } if (TileFilenames.Num() == 0) { Result.ResultCode = ELandscapeImportResult::Error; Result.ErrorMessage = LOCTEXT("FileReadErrorNoFilesFound", "No files found"); return Result; } // Check for int overflows due to too large SizeInTiles values from the filenames if (SizeInTiles.X > std::numeric_limits::max() / TileResolution.X || SizeInTiles.Y > std::numeric_limits::max() / TileResolution.Y || SizeInTiles.X <= 0 || SizeInTiles.Y <= 0) { Result.ResultCode = ELandscapeImportResult::Error; Result.ErrorMessage = LOCTEXT("FileReadErrorTileCoordsInvalid", "Invalid tiled image coordinates"); return Result; } Result.PossibleResolutions.Add(FLandscapeFileResolution(GetResolution().X, GetResolution().Y)); return Result; } template FLandscapeFileInfo FLandscapeTiledImage::Load(const TCHAR* Filename); template FLandscapeFileInfo FLandscapeTiledImage::Load(const TCHAR* Filename); void FLandscapeTiledImage::FindFiles(const TCHAR* FilenamePattern, TArray& OutPaths) { FString GlobFilename(FilenamePattern); for (const TPair& It : Tokens) { GlobFilename = GlobFilename.Replace(*It.Value, TEXT("*")); } OutPaths.Empty(); IFileManager::Get().FindFiles(OutPaths, *GlobFilename, true, false); } bool FLandscapeTiledImage::CheckTiledNamePath(const FString& Filename, FString& OutTiledFilenamePattern) { const FString Extension = FPaths::GetExtension(Filename); const FString Root = FPaths::GetPath(Filename); const FString BaseFilename = FPaths::GetBaseFilename(Filename); bool HasMatch = true; FString CurrentFilename = BaseFilename; int32 TokenIndex = 0; for (const TPair& It : Tokens) { while (true) { FRegexMatcher Matcher(FRegexPattern(GetTokenRegex(It.Key)), CurrentFilename); if (!Matcher.FindNext()) { break; } int32 MatchBegin = Matcher.GetMatchBeginning(); int32 MatchEnd = Matcher.GetMatchEnding(); CurrentFilename.RemoveAt(MatchBegin, MatchEnd - MatchBegin); CurrentFilename.InsertAt(MatchBegin, It.Key + It.Value); } } const FString PatternFilename = CurrentFilename + FString(".") + Extension; FString TiledFilenamePattern = FPaths::Combine(Root, PatternFilename); TArray FoundFiles; FLandscapeTiledImage::FindFiles(*TiledFilenamePattern, FoundFiles); const bool bFoundTiledFiles = FoundFiles.Num() > 0 && TiledFilenamePattern != Filename; if (bFoundTiledFiles ) { OutTiledFilenamePattern = TiledFilenamePattern; } return bFoundTiledFiles; } #undef LOCTEXT_NAMESPACE