Changes to apply material to large maps and bug fixes

This commit is contained in:
Roel Algaba Brizuela 2021-04-19 18:58:20 +02:00 committed by bernat
parent 1dc91fc7c0
commit b327e479d4
5 changed files with 237 additions and 41 deletions

View File

@ -9,6 +9,8 @@
#if WITH_EDITOR
#include "FileHelpers.h"
#endif
#include "JsonObject.h"
#include "JsonSerializer.h"
#include "UObject/ConstructorHelpers.h"
#include "Runtime/Engine/Classes/Kismet/GameplayStatics.h"
#include "Engine/StreamableManager.h"
@ -33,10 +35,48 @@ ULoadAssetMaterialsCommandlet::ULoadAssetMaterialsCommandlet()
#if WITH_EDITORONLY_DATA
void ULoadAssetMaterialsCommandlet::GenerateJsonInfoFile(const FString &MapName) {
//This function is needed to generate a json in the imported maps sub-directory "Config"
//for detecting if textures have to be created on construction
//and variables need to be set for road painter object.
//Also check if we need to render textures in runtime
//We have to find the sub-directory where the json has been created
//We can look up any of the .json we have generated
//and then go back 1 in the file path (.../map_package/Config/roadpainter_decals.json)
//We can't assume the root directory name is "map_package", because the user can change it
TArray<FString> FileList;
IFileManager::Get().FindFilesRecursive(FileList, *(FPaths::ProjectContentDir()),
*(FString("roadpainter_decals.json")), true, false, false);
//Get the absolute path to that file
FString JsonFilePath = *(IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*FileList[0]));
FString InfoFilePath = JsonFilePath.LeftChop((JsonFilePath.GetCharArray().Num() - 1) - JsonFilePath.Find("/", ESearchCase::Type::IgnoreCase, ESearchDir::FromEnd));
//We give it a unique name
InfoFilePath += "/roadpainter_info_" + World->GetMapName() + ".json";
UE_LOG(LogTemp, Warning, TEXT("%s"), *InfoFilePath);
//We write our variables
TSharedPtr<FJsonObject> RootObject = MakeShareable(new FJsonObject());
//Does the roadpainter need to generate new textures and apply changes on construction?
RootObject->SetBoolField("prepared_roadpainter", false);
//Are the roads rendered to texture already?
RootObject->SetBoolField("painted_roads", false);
//The name of the texture that will be used for rendering the roads to
RootObject->SetStringField("texture_name", " ");
FString JsonString;
TSharedRef<TJsonWriter<TCHAR>> JsonWriter = TJsonWriterFactory<TCHAR>::Create(&JsonString);
FJsonSerializer::Serialize(RootObject.ToSharedRef(), JsonWriter);
//Save JSON file
FFileHelper::SaveStringToFile(JsonString, *InfoFilePath);
}
void ULoadAssetMaterialsCommandlet::ApplyRoadPainterMaterials(const FString &LoadedMapName)
{
GenerateJsonInfoFile(LoadedMapName);
ARoadPainterWrapper *RoadPainterBp = World->SpawnActor<ARoadPainterWrapper>(RoadPainterSubclass);
UE_LOG(LogTemp, Log, TEXT("The name of the world where I'm spawning the blueprint is : %s"), *World->GetFullName());
if (RoadPainterBp)
{
//Needed to call events in editor-mode

View File

@ -43,6 +43,8 @@ public:
private:
void GenerateJsonInfoFile(const FString &MapName);
/// Loaded assets from any object library
UPROPERTY()
TArray<FAssetData> AssetDatas;

View File

@ -88,49 +88,164 @@ ARoadPainterWrapper::ARoadPainterWrapper(){
#endif
}
void ARoadPainterWrapper::ReadJsonAndPrepareRoadPainter(){
//Get road painter info file
FString JsonInfoFile;
TArray<FString> FileList;
IFileManager::Get().FindFilesRecursive(FileList, *(FPaths::ProjectContentDir()),
*(FString("roadpainter_info_" + GetWorld()->GetMapName() + ".json")), true, false, false);
if (FileList.Num() == 0) return;
FString AbsolutePathToFile = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*FileList[0]);
if (FFileHelper::LoadFileToString(JsonInfoFile, *AbsolutePathToFile)) {
TSharedPtr<FJsonObject> JsonParsed;
TSharedRef<TJsonReader<TCHAR>> JsonReader = TJsonReaderFactory<TCHAR>::Create(JsonInfoFile);
if (FJsonSerializer::Deserialize(JsonReader, JsonParsed))
{
ZSizeEvent();
FString TextureString;
//Get the string we need
bool IsRoadPainterReady = JsonParsed->GetBoolField(TEXT("prepared_roadpainter"));
if(IsRoadPainterReady == false){
//Generate the texture we need and save the name.
//This will be written into a .json file so we can look it up later
TextureString = GenerateTexture();
}
TArray<AActor*> FoundActors;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), AStaticMeshActor::StaticClass(), FoundActors);
AStaticMeshActor *RoadMeshActor = nullptr;
for (int32 i = 0; i < FoundActors.Num(); ++i)
{
RoadMeshActor = Cast<AStaticMeshActor>(FoundActors[i]);
if (RoadMeshActor)
{
if (RoadMeshActor->GetName().Contains("Roads_Road", ESearchCase::Type::CaseSensitive) == true)
{
//Create the dynamic material instance for the road (which will hold the map size and road texture)
UMaterialInstanceDynamic* MI = UMaterialInstanceDynamic::Create(RoadNodeMasterMaterial, NULL);
MI->CopyParameterOverrides((UMaterialInstance*)RoadNodePresetMaterial);
MI->SetScalarParameterValue(FName("Map units (CM)"), MapSize);
MI->SetTextureParameterValue(FName("Texture Mask"), RoadTexture);
RoadMeshActor->GetStaticMeshComponent()->SetMaterial(0, MI);
}
}
}
//We write our variables
TSharedPtr<FJsonObject> RootObject = MakeShareable(new FJsonObject());
//Are the textures ready?
RootObject->SetBoolField("prepared_roadpainter", true);
//Are the roads rendered to texture already?
RootObject->SetBoolField("painted_roads", false);
if (IsRoadPainterReady == false) {
//We set the name of the newly generated texture into the json file
RootObject->SetStringField("texture_name", TextureString);
}
else {
TextureString = JsonParsed->GetStringField("texture_name");
RootObject->SetStringField("texture_name", TextureString);
}
FString JsonString;
TSharedRef<TJsonWriter<TCHAR>> JsonWriter = TJsonWriterFactory<TCHAR>::Create(&JsonString);
FJsonSerializer::Serialize(RootObject.ToSharedRef(), JsonWriter);
//Save JSON file
FFileHelper::SaveStringToFile(JsonString, *AbsolutePathToFile);
}
}
}
void ARoadPainterWrapper::ReadJsonAndPaintRoads() {
//Get road painter info file
FString JsonInfoFile;
FString MapName = GetWorld()->GetMapName();
MapName = MapName.RightChop((MapName.GetCharArray().Num() - 1) - (MapName.Find("_", ESearchCase::Type::IgnoreCase, ESearchDir::Type::FromEnd) - 1));
TArray<FString> FileList;
IFileManager::Get().FindFilesRecursive(FileList, *(FPaths::ProjectContentDir()),
*(FString("roadpainter_info_" + MapName + ".json")), true, false, false);
UE_LOG(LogTemp, Warning, TEXT("%s"), *MapName);
if (FileList.Num() == 0) return;
FString AbsolutePathToFile = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*FileList[0]);
if (FFileHelper::LoadFileToString(JsonInfoFile, *AbsolutePathToFile)) {
TSharedPtr<FJsonObject> JsonParsed;
TSharedRef<TJsonReader<TCHAR>> JsonReader = TJsonReaderFactory<TCHAR>::Create(JsonInfoFile);
if (FJsonSerializer::Deserialize(JsonReader, JsonParsed))
{
//Get the boolean we need
bool AreRoadsPainted = JsonParsed->GetBoolField(TEXT("painted_roads"));
if (AreRoadsPainted == false) {
PaintAllRoadsEvent();
//We write our variables
TSharedPtr<FJsonObject> RootObject = MakeShareable(new FJsonObject());
//Are the textures already loaded?
RootObject->SetBoolField("prepared_roadpainter", true);
//Are the roads rendered to texture already?
RootObject->SetBoolField("painted_roads", true);
//Does the roadpainter need to generate new textures and apply changes on construction?
RootObject->SetStringField("texture_name", JsonParsed->GetStringField("texture_name"));
FString JsonString;
TSharedRef<TJsonWriter<TCHAR>> JsonWriter = TJsonWriterFactory<TCHAR>::Create(&JsonString);
FJsonSerializer::Serialize(RootObject.ToSharedRef(), JsonWriter);
//Save JSON file
FFileHelper::SaveStringToFile(JsonString, *AbsolutePathToFile);
}
bIsRenderedToTexture = true;
}
}
}
void ARoadPainterWrapper::BeginPlay()
{
Super::BeginPlay();
if(bIsRenderedToTexture == false)
{
PaintAllRoadsEvent();
bIsRenderedToTexture = true;
if (bIsRenderedToTexture == false) {
ReadJsonAndPaintRoads();
}
}
void ARoadPainterWrapper::GenerateDynamicAssets()
{
ZSizeEvent();
if(RoadTexture == nullptr) GenerateTexture();
TArray<AActor*> FoundActors;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), AStaticMeshActor::StaticClass(), FoundActors);
AStaticMeshActor *RoadMeshActor = nullptr;
for (int32 i = 0; i < FoundActors.Num(); ++i)
{
RoadMeshActor = Cast<AStaticMeshActor>(FoundActors[i]);
if (RoadMeshActor)
{
if (RoadMeshActor->GetName().Contains("Roads_Road", ESearchCase::Type::CaseSensitive) == true)
{
//Create the dynamic material instance for the road (which will hold the map size and road texture)
UMaterialInstanceDynamic* MI = UMaterialInstanceDynamic::Create(RoadNodeMasterMaterial, NULL);
MI->CopyParameterOverrides((UMaterialInstance*)RoadNodePresetMaterial);
MI->SetScalarParameterValue(FName("Map units (CM)"), MapSize);
MI->SetTextureParameterValue(FName("Texture Mask"), RoadTexture);
RoadMeshActor->GetStaticMeshComponent()->SetMaterial(0, MI);
}
}
}
ReadJsonAndPrepareRoadPainter();
}
void ARoadPainterWrapper::GenerateTexture()
const FString ARoadPainterWrapper::GenerateTexture()
{
//We have to find the directory where the maps have been imported
//For this, we can track any of the .json files we have generated
//and then go back 2 in the file path (.../map_package/Config/roadpainter_decals.json)
//We can't assume the directory name is "map_package", because the user can change it
TArray<FString> FileList;
IFileManager::Get().FindFilesRecursive(FileList, *(FPaths::ProjectContentDir()),
*(FString("roadpainter_decals.json")), true, false, false);
//Get the absolute path to that file
FString JsonFilePath = *(IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*FileList[0]));
FString Path = JsonFilePath.LeftChop((JsonFilePath.GetCharArray().Num() - 1) - JsonFilePath.Find("/Config/", ESearchCase::Type::CaseSensitive, ESearchDir::FromEnd));
FString ImportPackageName = Path.RightChop(Path.Find("/", ESearchCase::IgnoreCase, ESearchDir::FromEnd) + 1);
//Create a string containing the texture's path
FString PackageName = TEXT("/Game/map_package/RoadPainterTextures/");
FString PackageName = TEXT("/Game/" + ImportPackageName + "/RoadPainterTextures/");
FString BaseTextureName = FString("RoadMapTexture");
PackageName += BaseTextureName;
@ -140,6 +255,7 @@ void ARoadPainterWrapper::GenerateTexture()
//Create a unique name for our asset. For example, if a texture named RoadMapTexture already exists the editor
//will name the new texture as "RoadMapTexture_1"
const FName TextureName = MakeUniqueObjectName(Package, UTextureRenderTarget2D::StaticClass(), FName(*BaseTextureName));
FString TextureString = TextureName.ToString();
Package->FullyLoad();
RoadTexture = NewObject<UTextureRenderTarget2D>(Package, TextureName, RF_Public | RF_Standalone | RF_MarkAsRootSet);
@ -151,6 +267,8 @@ void ARoadPainterWrapper::GenerateTexture()
RoadTexture->RenderTargetFormat = ETextureRenderTargetFormat::RTF_RGBA8;
RoadTexture->SizeX = 2048;
RoadTexture->SizeY = 2048;
RoadTexture->AddressX = TextureAddress::TA_Wrap;
RoadTexture->AddressY = TextureAddress::TA_Wrap;
//Initialize the platform data to store necessary information regarding our texture asset
InternalTexture2D->AddToRoot();
@ -159,9 +277,19 @@ void ARoadPainterWrapper::GenerateTexture()
InternalTexture2D->PlatformData->SizeY = 2048;
InternalTexture2D->AddressX = TextureAddress::TA_Wrap;
InternalTexture2D->AddressY = TextureAddress::TA_Wrap;
#if WITH_EDITORONLY_DATA
InternalTexture2D->Source.Init(2048, 2048, 1, 1, ETextureSourceFormat::TSF_RGBA8);
#endif
InternalTexture2D->UpdateResource();
//Set the texture render target internal texture
RoadTexture->UpdateTexture2D(InternalTexture2D, ETextureSourceFormat::TSF_RGBA8);
#if WITH_EDITORONLY_DATA
RoadTexture->Source.Init(2048, 2048, 1, 1, ETextureSourceFormat::TSF_RGBA8);
#endif
RoadTexture->UpdateResource();
Package->MarkPackageDirty();
//Notify the editor we created a new asset
@ -170,7 +298,10 @@ void ARoadPainterWrapper::GenerateTexture()
//Auto-save the new asset
FString PackageFilename = FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension());
UPackage::SavePackage(Package, RoadTexture, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, *PackageFilename, GError, nullptr, true, true, SAVE_NoError);
return TextureString;
}
return TextureString;
}
void ARoadPainterWrapper::ReadConfigFile(const FString &CurrentMapName)

View File

@ -138,7 +138,15 @@ private:
/// Create a procedural texture for painting the road maps
/// and save it to disk.
void GenerateTexture();
const FString GenerateTexture();
/// Read the Json info file to determine if we have to
/// generate textures and prepare variables on construction
void ReadJsonAndPrepareRoadPainter();
/// Read the Json info file to determine if we have to
/// paint the roads at runtime or it has already been done
void ReadJsonAndPaintRoads();
/// Function to read 3D vectors from a JSON file
FVector ReadVectorFromJsonObject(TSharedPtr<FJsonObject> JsonObject);

View File

@ -34,10 +34,19 @@ def get_packages_json_list(folder):
for root, _, filenames in os.walk(folder):
for filename in fnmatch.filter(filenames, "*.json"):
json_files.append([root, filename])
if filename != "roadpainter_decals.json":
json_files.append([root, filename])
return json_files
def get_decals_json_file(folder):
for root, _, filenames in os.walk(folder):
for filename in fnmatch.filter(filenames, "roadpainter_decals.json"):
return filename
return ""
def generate_json_package(folder, package_name, use_carla_materials):
"""Generate a .json file with all the maps it founds on the folder
and subfolders. A map is a .fbx and a .xodr with the same name.
@ -443,20 +452,26 @@ def main():
'--no-carla-materials',
action='store_false',
help='user Carla materials')
argparser.add_argument(
'--json-only',
action='store_true',
help='Create JSON files only')
args = argparser.parse_known_args()[0]
import_folder = os.path.join(CARLA_ROOT_PATH, "Import")
json_list = get_packages_json_list(import_folder)
#If we only gather one file or none from the json file search
#we need to create the missing files,
#so might as well create them all again
if (len(json_list) < 2):
decals_json = get_decals_json_file(import_folder)
if len(json_list) == 0:
json_list = generate_json_package(import_folder, args.package, args.no_carla_materials)
if len(decals_json) == 0:
decals_json_file = generate_decals_file(import_folder)
copy_roadpainter_config_files(args.package)
import_assets_from_json_list(json_list)
if args.json_only == False:
copy_roadpainter_config_files(args.package)
import_assets_from_json_list(json_list)
if __name__ == '__main__':
main()