nDisplay features

- warp & blend support
- in camera pixel
- pfm generation
- mpcdi support

#jira UE-77525
#rb none

#ROBOMERGE-SOURCE: CL 7438705 in //UE4/Release-4.23/...
#ROBOMERGE-BOT: RELEASE (Release-4.23 -> Main) (v371-7306989)

[CL 7438706 by simon tourangeau in Main branch]
This commit is contained in:
simon tourangeau
2019-07-18 12:00:53 -04:00
parent ce4165b884
commit 9889ad055a
206 changed files with 6701 additions and 1821 deletions
@@ -0,0 +1,39 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.IO;
public class PFMExporter : ModuleRules
{
public PFMExporter(ReadOnlyTargetRules ROTargetRules) : base(ROTargetRules)
{
PublicDefinitions.Add("PFMExporter_STATIC");
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"CoreUObject",
"Engine",
});
PrivateDependencyModuleNames.AddRange(
new string[]
{
"Core",
"CoreUObject",
"Engine",
"Projects",
"RenderCore",
"RHI",
"UtilityShaders"
}
);
if (Target.bBuildEditor == true)
{
PrivateDependencyModuleNames.Add("UnrealEd");
}
}
}
@@ -0,0 +1,53 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "Blueprints/PFMExporterBlueprintAPIImpl.h"
#include "IPFMExporter.h"
#include "Engine/StaticMesh.h"
#include "UObject/Package.h"
#include "Components/StaticMeshComponent.h"
#include "Components/SceneComponent.h"
bool UPFMExporterAPIImpl::ExportPFM(
UStaticMeshComponent* SrcMeshComponent,
USceneComponent* PFMOrigin,
int Width,
int Height,
const FString& FileName
)
{
if (SrcMeshComponent==nullptr)
{
//! handle error
return false;
}
// Use attached root as origin
//! All geometry links scaled, etc at this point. Finally we send MeshToOrigin matrix to shader (transfrom from mesh space to Origin)
USceneComponent* OriginComp = (PFMOrigin) ? PFMOrigin : SrcMeshComponent->GetAttachParent();
const FTransform& OriginToWorldTransform = (OriginComp ? OriginComp->GetComponentTransform() : FTransform::Identity);
FTransform MeshToWorldTransform = SrcMeshComponent->GetComponentTransform();
FMatrix WorldToOrigin = OriginToWorldTransform.ToInverseMatrixWithScale();
FMatrix MeshToWorld = MeshToWorldTransform.ToMatrixWithScale();
FMatrix MeshToOrigin = MeshToWorld * WorldToOrigin;
UStaticMesh* StaticMesh = SrcMeshComponent->GetStaticMesh();
if(StaticMesh==nullptr)
{
//! Handle error
return false;
}
if (IPFMExporter::Get().ExportPFM(&StaticMesh->GetLODForExport(0), MeshToOrigin, Width, Height, FileName))
{
return true;
}
//! handle error
return false;
}
@@ -0,0 +1,45 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "InputCoreTypes.h"
#include "Blueprints/IPFMExporterBlueprintAPI.h"
#include "PFMExporterBlueprintAPIImpl.generated.h"
/**
* Blueprint API interface implementation
*/
UCLASS()
class UPFMExporterAPIImpl
: public UObject
, public IPFMExporterBlueprintAPI
{
GENERATED_BODY()
public:
/**
* Generate PFM file from static mesh.
* The UV channel must be defined, assigned range 0..1 used as screen surface.
* Origin assigned by function arg, or by default used mesh parent.
*
* @param SrcMesh - Static mesh with assigned UV channel, used as export source of PFM file
* @param Origin - (Optional) Custom cave origin node, if not defined, used SrcMesh parent
* @param Width - Output PFM mesh texture width
* @param Height - Output PFM mesh texture height
* @param FileName - Output PFM file name
*
* @return true, if success
*/
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Export Static Mesh to PFM file"), Category = "PFMExporter")
virtual bool ExportPFM(
UStaticMeshComponent* SrcMesh,
USceneComponent* Origin,
int Width,
int Height,
const FString& FileName
) override;
};
@@ -0,0 +1,17 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "Blueprints/PFMExporterBlueprintLib.h"
#include "Blueprints/PFMExporterBlueprintAPIImpl.h"
#include "UObject/Package.h"
UPFMExporterBlueprintLib::UPFMExporterBlueprintLib(class FObjectInitializer const & ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UPFMExporterBlueprintLib::GetAPI(TScriptInterface<IPFMExporterBlueprintAPI>& OutAPI)
{
static UPFMExporterAPIImpl* Obj = NewObject<UPFMExporterAPIImpl>(GetTransientPackage(), NAME_None, RF_MarkAsRootSet);
OutAPI = Obj;
}
@@ -0,0 +1,5 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "PFMExporterLog.h"
DEFINE_LOG_CATEGORY(LogPFMExporter);
@@ -0,0 +1,11 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
// Plugin-wide log categories
#if UE_BUILD_SHIPPING
DECLARE_LOG_CATEGORY_EXTERN(LogPFMExporter, Warning, Warning);
#else
DECLARE_LOG_CATEGORY_EXTERN(LogPFMExporter, Log, All);
#endif
@@ -0,0 +1,96 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "PFMExporterMesh.h"
#include "PFMExporterLog.h"
#include "Engine/Engine.h"
#include "Engine/RendererSettings.h"
#include "Stats/Stats.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/Paths.h"
#include "Misc/FileHelper.h"
#include "RendererInterface.h"
#include "CommonRenderResources.h"
#include "RenderResource.h"
#include "SceneTypes.h"
static const EPixelFormat PFMTextureFormat = PF_A32B32G32R32F;
static const uint32 PFMPixelSize = 4 * 4;
FPFMExporterMesh::FPFMExporterMesh(int Width, int Height)
: DimWidth(Width)
, DimHeight(Height)
{
}
FPFMExporterMesh::~FPFMExporterMesh()
{
}
bool FPFMExporterMesh::SaveToFile(const FString& FileName)
{
TUniquePtr<FArchive> FileWriter(IFileManager::Get().CreateFileWriter(*FileName));
if (!FileWriter.IsValid())
{
UE_LOG(LogPFMExporter, Error, TEXT("Couldn't create a file writer %s"), *FileName);
return false;
}
const FString Header = FString::Printf(TEXT("PF%c%d %d%c-1%c"), 0x0A, DimWidth, DimHeight, 0x0A, 0x0A);
FileWriter->Serialize(TCHAR_TO_ANSI(*Header), Header.Len());
FileWriter->Serialize((void*)PFMPoints.GetData(), PFMPoints.Num() * 3 * sizeof(float));
UE_LOG(LogPFMExporter, Log, TEXT("Exported PFM file %s"), *FileName);
return true;
}
bool FPFMExporterMesh::BeginExport_RenderThread(FRHICommandListImmediate& RHICmdList)
{
// Allocate RT+SR RHI resources
FRHIResourceCreateInfo CreateInfo;
RHICreateTargetableShaderResource2D(DimWidth, DimHeight, PFMTextureFormat, 1, TexCreate_None, TexCreate_RenderTargetable, true, CreateInfo, RenderTargetTexture, ShaderResourceTexture);
return true;
}
bool FPFMExporterMesh::FinishExport_RenderThread(FRHICommandListImmediate& RHICmdList)
{
//Copy texture to SR
RHICmdList.CopyToResolveTarget(RenderTargetTexture, ShaderResourceTexture, FResolveParams());
RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThreadFlushResources);
// Extract result to memory
PFMPoints.Empty();
PFMPoints.Reserve(DimHeight*DimWidth);
uint32 SrcStride;
uint8* SrcBuffer = (uint8*)RHICmdList.LockTexture2D(ShaderResourceTexture, 0, EResourceLockMode::RLM_ReadOnly, SrcStride, false);
if(SrcBuffer!=nullptr)
{
for (int32 y = 0; y < DimHeight; ++y)
{
for (int32 x = 0; x < DimWidth; ++x)
{
float* Src = (float*)(SrcBuffer + x * PFMPixelSize + y * SrcStride);
float Alpha = Src[3];
if (Alpha==0)
{
PFMPoints.Add(FVector(NAN, NAN, NAN));// Undefined geometry, return NAN
}
else
{
PFMPoints.Add(FVector(Src[0], Src[1], Src[2])); // Defined, save this point
}
}
}
}
RHICmdList.UnlockTexture2D(ShaderResourceTexture, 0, false);
// Release RHI resources
RenderTargetTexture.SafeRelease();
ShaderResourceTexture.SafeRelease();
return true;
}
@@ -0,0 +1,40 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "RHI.h"
#include "RHICommandList.h"
class FPFMExporterMesh
{
TArray<FVector> PFMPoints;
int DimWidth;
int DimHeight;
public:
FPFMExporterMesh(int Width, int Height);
~FPFMExporterMesh();
bool BeginExport_RenderThread(FRHICommandListImmediate& RHICmdList);
bool FinishExport_RenderThread(FRHICommandListImmediate& RHICmdList);
bool SaveToFile(const FString& FileName);
inline bool IsValid() const
{
return PFMPoints.Num()>0;
}
inline FIntRect GetSize() const
{
return FIntRect(FIntPoint(0, 0), FIntPoint(DimWidth, DimHeight));
}
inline FTexture2DRHIRef GetTargetableTexture() const
{
return RenderTargetTexture;
}
private:
FTexture2DRHIRef RenderTargetTexture;
FTexture2DRHIRef ShaderResourceTexture;
};
@@ -0,0 +1,59 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "PFMExporterModule.h"
#include "PFMExporterShader.h"
#include "PFMExporterMesh.h"
#include "PFMExporterLog.h"
#include "Interfaces/IPluginManager.h"
#include "Misc/Paths.h"
#include "ShaderCore.h"
#include "RHICommandList.h"
//////////////////////////////////////////////////////////////////////////////////////////////
// IModuleInterface
//////////////////////////////////////////////////////////////////////////////////////////////
#define WARPUTILS_SHADERS_MAP TEXT("/Plugin/WarpUtils")
void FPFMExporterModule::StartupModule()
{
if (!AllShaderSourceDirectoryMappings().Contains(WARPUTILS_SHADERS_MAP))
{
FString PluginShaderDir = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("WarpUtils"))->GetBaseDir(), TEXT("Shaders"));
AddShaderSourceDirectoryMapping(WARPUTILS_SHADERS_MAP, PluginShaderDir);
}
}
void FPFMExporterModule::ShutdownModule()
{
}
//////////////////////////////////////////////////////////////////////////////////////////////
// IPFMExporter
//////////////////////////////////////////////////////////////////////////////////////////////
bool FPFMExporterModule::ExportPFM(const FStaticMeshLODResources* SrcMeshResource, const FMatrix& MeshToOrigin, int PFMWidth, int PFMHeight, const FString& FilePath)
{
FPFMExporterMesh* PFMMesh = new FPFMExporterMesh(PFMWidth, PFMHeight);
ENQUEUE_RENDER_COMMAND(CaptureCommand)([SrcMeshResource, MeshToOrigin, PFMMesh, FilePath](FRHICommandListImmediate& RHICmdList)
{
if (FPFMExporterShader::ApplyPFMExporter_RenderThread(RHICmdList, *SrcMeshResource, MeshToOrigin, *PFMMesh))
{
// Capture is ok, save result to file
PFMMesh->SaveToFile(FilePath);
}
else
{
//! handle error
UE_LOG(LogPFMExporter, Error, TEXT("Fail to capture pfm mesh to file: %s"), *FilePath);
}
// Release pfm data
delete PFMMesh;
});
return true;
}
IMPLEMENT_MODULE(FPFMExporterModule, PFMExporter);
@@ -0,0 +1,27 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "IPFMExporter.h"
class FPFMExporterModule
: public IPFMExporter
{
public:
//////////////////////////////////////////////////////////////////////////////////////////////
// IModuleInterface
//////////////////////////////////////////////////////////////////////////////////////////////
virtual void StartupModule() override;
virtual void ShutdownModule() override;
public:
//////////////////////////////////////////////////////////////////////////////////////////////
// PFMExporter
//////////////////////////////////////////////////////////////////////////////////////////////
virtual bool ExportPFM(
const FStaticMeshLODResources* SrcMeshResource,
const FMatrix& MeshToOrigin,
int PFMWidth,
int PFMHeight,
const FString& LocalFileName
) override;
};
@@ -0,0 +1,184 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "PFMExporterShader.h"
#include "PFMExporterMesh.h"
#include "Shader.h"
#include "GlobalShader.h"
#include "ShaderParameters.h"
#include "ShaderParameterUtils.h"
#include "PixelShaderUtils.h"
#include "RHIResources.h"
#include "CommonRenderResources.h"
#include "Engine/StaticMesh.h"
#include "HAL/IConsoleManager.h"
#include "Rendering/StaticMeshVertexBuffer.h"
#include "Components/StaticMeshComponent.h"
#include "Components/SceneComponent.h"
#define PFMExporterShaderFileName TEXT("/Plugin/WarpUtils/Private/PFMExporterShaders.usf")
class FPFMExporterVS
: public FGlobalShader
{
DECLARE_SHADER_TYPE(FPFMExporterVS, Global);
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return true;
}
/** Default constructor. */
FPFMExporterVS()
{
}
public:
/** Initialization constructor. */
FPFMExporterVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
: FGlobalShader(Initializer)
{
MeshToPFMMatrixParameter.Bind(Initializer.ParameterMap, TEXT("MeshToPFMMatrix"));
}
template<typename TShaderRHIParamRef>
void SetMeshToPFMMatrix(FRHICommandListImmediate& RHICmdList, const TShaderRHIParamRef ShaderRHI, const FMatrix& MeshToPFMMatrix)
{
SetShaderValue(RHICmdList, ShaderRHI, MeshToPFMMatrixParameter, MeshToPFMMatrix);
}
virtual bool Serialize(FArchive& Ar) override
{
bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
Ar
<< MeshToPFMMatrixParameter;
return bShaderHasOutdatedParameters;
}
private:
FShaderParameter MeshToPFMMatrixParameter;
};
class FPFMExporterPS
: public FGlobalShader
{
DECLARE_SHADER_TYPE(FPFMExporterPS, Global);
public:
FPFMExporterPS()
{
}
FPFMExporterPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
: FGlobalShader(Initializer)
{
}
public:
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM4);
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
}
};
// Implement shaders inside UE4
IMPLEMENT_SHADER_TYPE(, FPFMExporterVS, PFMExporterShaderFileName, TEXT("PFMExporterUV_VS"), SF_Vertex);
IMPLEMENT_SHADER_TYPE(, FPFMExporterPS, PFMExporterShaderFileName, TEXT("PFMExporterPassthrough_PS"), SF_Pixel);
bool FPFMExporterShader::ApplyPFMExporter_RenderThread(
FRHICommandListImmediate& RHICmdList,
const FStaticMeshLODResources& SrcMeshResource,
const FMatrix& MeshToOrigin,
FPFMExporterMesh& DstPfmMesh
)
{
check(IsInRenderingThread());
//@todo: Here we can add UV channel definition (multiple PFM from one mesh with multiple materials(UV))
int UVIndex = 0; //! Support multimaterial:
const FPositionVertexBuffer& VertexPosition = SrcMeshResource.VertexBuffers.PositionVertexBuffer;
const FStaticMeshVertexBuffer& VertexBuffer = SrcMeshResource.VertexBuffers.StaticMeshVertexBuffer;
const FRawStaticIndexBuffer& IndexBuffer = SrcMeshResource.IndexBuffer;
uint32 NumTriangles = IndexBuffer.GetNumIndices() / 3; //! Now by default no triangle strip supported
uint32 NumVertices = VertexBuffer.GetNumVertices();
// Initialize data for PFm (RT textures, etc)
DstPfmMesh.BeginExport_RenderThread(RHICmdList);
FRHIResourceCreateInfo CreateInfo;
FVertexBufferRHIRef VertexBufferRHI = RHICreateVertexBuffer(sizeof(FFilterVertex) * NumVertices, BUF_Dynamic, CreateInfo);
{//Fill buffer with vertex+selected UV channel:
void* VoidPtr = RHILockVertexBuffer(VertexBufferRHI, 0, sizeof(FFilterVertex) * NumVertices, RLM_WriteOnly);
FFilterVertex* pVertices = reinterpret_cast<FFilterVertex*>(VoidPtr);
for (uint32 i = 0; i < NumVertices; i++)
{
FFilterVertex& Vertex = pVertices[i];
Vertex.Position = VertexPosition.VertexPosition(i);
Vertex.UV = VertexBuffer.GetVertexUV(i, UVIndex); // Get UV from selected channel
}
RHIUnlockVertexBuffer(VertexBufferRHI);
}
FRHIIndexBuffer* IndexBufferRHI = IndexBuffer.IndexBufferRHI;
// Build transform matrix for mesh:
static float Scale = 1.0f; // Default export scale is unreal, cm
static FMatrix MPCDIToGame = FMatrix(
FPlane(0.f, Scale, 0.f, 0.f),
FPlane(0.f, 0.f, Scale, 0.f),
FPlane(-Scale, 0.f, 0.f, 0.f),
FPlane(0.f, 0.f, 0.f, 1.f));
static FMatrix GameToMPCDI = MPCDIToGame.Inverse();
FMatrix MeshToPFMMatrix = MeshToOrigin * GameToMPCDI;
{// Do remap single render pass
FRHIRenderPassInfo RPInfo(DstPfmMesh.GetTargetableTexture(), ERenderTargetActions::Load_Store);
RHICmdList.BeginRenderPass(RPInfo, TEXT("DisplayClusterPFMExporterShader"));
{
FIntRect DstRect = DstPfmMesh.GetSize();
RHICmdList.SetViewport(DstRect.Min.X, DstRect.Min.Y, 0.0f, DstRect.Max.X, DstRect.Max.Y, 1.0f);
//DrawClearQuad(RHICmdList, FLinearColor::Black); //!
TShaderMap<FGlobalShaderType>* ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel);
TShaderMapRef<FPFMExporterVS> VertexShader(ShaderMap);
TShaderMapRef<FPFMExporterPS> PixelShader(ShaderMap);
{// Set the graphic pipeline state.
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Never>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState <>::GetRHI();
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;// GetVertexDeclarationFVector4();
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
}
VertexShader->SetMeshToPFMMatrix(RHICmdList, VertexShader->GetVertexShader(), MeshToPFMMatrix);
// Render mesh to PFM texture:
RHICmdList.SetStreamSource(0, VertexBufferRHI, 0);
RHICmdList.DrawIndexedPrimitive(IndexBufferRHI, 0, 0, NumVertices, 0, NumTriangles, 1);
}
RHICmdList.EndRenderPass();
}
// Extract data from texture to memory:
return DstPfmMesh.FinishExport_RenderThread(RHICmdList);
}
@@ -0,0 +1,22 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "RHI.h"
#include "RHICommandList.h"
#include "RenderResource.h"
#include "IPFMExporter.h"
class FPFMExporterMesh;
class FPFMExporterShader
{
public:
static bool ApplyPFMExporter_RenderThread(
FRHICommandListImmediate& RHICmdList,
const FStaticMeshLODResources& SrcMeshResource,
const FMatrix& MeshToOrigin,
FPFMExporterMesh& DstPfmMesh
);
};
@@ -0,0 +1,46 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "RHI.h"
#include "RHIResources.h"
#include "IPFMExporterBlueprintAPI.generated.h"
class UStaticMeshComponent;
class USceneComponent;
UINTERFACE(meta = (CannotImplementInterfaceInBlueprint))
class UPFMExporterBlueprintAPI : public UInterface
{
GENERATED_BODY()
};
class IPFMExporterBlueprintAPI
{
GENERATED_BODY()
public:
/**
* Generate PFM file from static mesh.
* The UV channel must be defined, assigned range 0..1 used as screen surface.
* Origin assigned by function arg, or by default used mesh parent.
*
* @param SrcMesh - Static mesh with assigned UV channel, used as export source of PFM file
* @param Origin - (Optional) Custom cave origin node, if not defined, used SrcMesh parent
* @param Width - Output PFM mesh texture width
* @param Height - Output PFM mesh texture height
* @param FileName - Output PFM file name
*
* @return true, if success
*/
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Export Static Mesh to PFM file"), Category = "PFMExporter")
virtual bool ExportPFM(
UStaticMeshComponent* SrcMesh,
USceneComponent* Origin,
int Width,
int Height,
const FString& FileName
) = 0;
};
@@ -0,0 +1,22 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Blueprints/IPFMExporterBlueprintAPI.h"
#include "PFMExporterBlueprintLib.generated.h"
/**
* Blueprint API function library
*/
UCLASS()
class UPFMExporterBlueprintLib
: public UBlueprintFunctionLibrary
{
GENERATED_UCLASS_BODY()
public:
/** Return Display Cluster API interface. */
UFUNCTION(BlueprintPure, meta = (DisplayName = "PFMExporter Module API"), Category = "nDisplay")
static void GetAPI(TScriptInterface<IPFMExporterBlueprintAPI>& OutAPI);
};
@@ -0,0 +1,57 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"
struct FStaticMeshLODResources;
class IPFMExporter : public IModuleInterface
{
public:
static constexpr auto ModuleName = TEXT("PFMExporter");
public:
/**
* Singleton-like access to this module's interface. This is just for convenience!
* Beware of calling this during the shutdown phase, though. Your module might have been unloaded already.
*
* @return Returns singleton instance, loading the module on demand if needed
*/
static inline IPFMExporter& Get()
{
return FModuleManager::LoadModuleChecked<IPFMExporter>(IPFMExporter::ModuleName);
}
/**
* Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true.
*
* @return True if the module is loaded and ready to use
*/
static inline bool IsAvailable()
{
return FModuleManager::Get().IsModuleLoaded(IPFMExporter::ModuleName);
}
public:
/**
* Generate PFM file from static mesh.
* The UV channel must be defined, assigned range 0..1 used as screen surface.
*
* @param SrcMeshResource - Source mesh with defined UV, used as PFM 3d source
* @param MeshToOrigin - Transform matrix convert mesh vertices to cave origin space
* @param PFMWidth - Output PFM mesh texture width
* @param PFMHeight - Output PFM mesh texture height
* @param LocalFileName - Output PFM file name (supported relative paths)
*
* @return - true, if export success
*/
virtual bool ExportPFM(
const FStaticMeshLODResources* SrcMeshResource,
const FMatrix& MeshToOrigin,
int PFMWidth,
int PFMHeight,
const FString& LocalFileName
) = 0;
};
@@ -0,0 +1,218 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "Blueprints/WarpUtilsBlueprintLibrary.h"
#include "GameFramework/Actor.h"
#include "HAL/FileManager.h"
#include "Serialization/Archive.h"
#include "WarpUtilsLog.h"
bool UWarpUtilsBlueprintLibrary::SavePFM(const FString& File, const int TexWidth, const int TexHeight, const TArray<FVector>& Vertices)
{
if (Vertices.Num() != (TexWidth * TexHeight))
{
UE_LOG(LogWarpUtilsBlueprint, Error, TEXT("Wrong pixels amount: TexWidth %d, TexHeight %d, TexWidth*TexHeight == %d, Vertices %d"),
TexWidth, TexHeight, TexWidth * TexHeight, Vertices.Num());
return false;
}
const size_t ArraySize = 3 * Vertices.Num();
TArray<float> Dataset;
Dataset.AddUninitialized(ArraySize);
float Scale = 1.0f;
FMatrix m = FMatrix(
FPlane(0.f, Scale, 0.f, 0.f),
FPlane(0.f, 0.f, Scale, 0.f),
FPlane(-Scale, 0.f, 0.f, 0.f),
FPlane(0.f, 0.f, 0.f, 1.f));
int len = Vertices.Num();
for (int i = 0; i < len; i++)
{
FVector v = Vertices[i];
if (!FMath::IsNaN(v.X))
{
v = m.InverseTransformPosition(v);
}
Dataset[i * 3 + 0] = v.X;
Dataset[i * 3 + 1] = v.Y;
Dataset[i * 3 + 2] = v.Z;
}
TUniquePtr<FArchive> FileWriter(IFileManager::Get().CreateFileWriter(*File));
if (!FileWriter.IsValid())
{
UE_LOG(LogWarpUtilsBlueprint, Error, TEXT("Couldn't create a file writer %s"), *File);
return false;
}
const FString Header = FString::Printf(TEXT("PF%c%d %d%c-1%c"), 0x0A, TexWidth, TexHeight, 0x0A, 0x0A);
FileWriter->Serialize(TCHAR_TO_ANSI(*Header), Header.Len());
FileWriter->Serialize((void*)Dataset.GetData(), ArraySize * sizeof(float));
return true;
}
bool UWarpUtilsBlueprintLibrary::SavePFMEx(const FString& File, const int TexWidth, const int TexHeight, const TArray<FVector>& Vertices, const TArray<bool>& VertexValidityFlags)
{
if (Vertices.Num() != (TexWidth * TexHeight))
{
UE_LOG(LogWarpUtilsBlueprint, Error, TEXT("Wrong pixels amount: TexWidth %d, TexHeight %d, TexWidth*TexHeight == %d, Vertices %d"),
TexWidth, TexHeight, TexWidth * TexHeight, Vertices.Num());
return false;
}
if (Vertices.Num() != VertexValidityFlags.Num())
{
UE_LOG(LogWarpUtilsBlueprint, Error, TEXT("Vertices amount not equals to the validity flags amount (%d != %d)"), Vertices.Num(), VertexValidityFlags.Num());
return false;
}
// Explicit XYZ set to NAN instead of FVector(NAN, NAN, NAN) allows to avoid any FVector internal NAN checks
FVector NanVector;
NanVector.X = NanVector.Y = NanVector.Z = NAN;
// Replace invalid vertices with NaN values
TArray<FVector> FixedData(Vertices);
for (int i = 0; i < FixedData.Num(); ++i)
{
FixedData[i] = (VertexValidityFlags[i] ? FixedData[i] : NanVector);
}
// Save data
return SavePFM(File, TexWidth, TexHeight, FixedData);
}
bool UWarpUtilsBlueprintLibrary::GeneratePFM(
const FString& File,
const FVector& StartLocation, const FRotator& StartRotation, const AActor* PFMOrigin,
const int TilesHorizontal, const int TilesVertical, const float ColumnAngle,
const float TileSizeHorizontal, const float TileSizeVertical, const int TilePixelsHorizontal, const int TilePixelsVertical, const bool AddMargin)
{
// Check input data that is used in this function
if (TilesHorizontal < 1 || TilesVertical < 1)
{
UE_LOG(LogWarpUtilsBlueprint, Error, TEXT("Both horizontal and vertical tiles amounts must be more than 1"));
return false;
}
// Amount of validity flags must be the same as the tiles amount
const int TilesAmount = TilesHorizontal * TilesVertical;
// Set all validity flags to 'true' by default
TArray<bool> TilesValidityFlags;
TilesValidityFlags.AddUninitialized(TilesAmount);
for (bool& Item : TilesValidityFlags)
{
Item = true;
}
// Call the implementation function
return GeneratePFMEx(File, StartLocation, StartRotation, PFMOrigin, TilesHorizontal, TilesVertical, ColumnAngle, TileSizeHorizontal, TileSizeVertical, TilePixelsHorizontal, TilePixelsVertical, AddMargin, TilesValidityFlags);
}
bool UWarpUtilsBlueprintLibrary::GeneratePFMEx(
const FString& File,
const FVector& StartLocation, const FRotator& StartRotation, const AActor* PFMOrigin,
const int TilesHorizontal, const int TilesVertical, const float ColumnAngle,
const float TileSizeHorizontal, const float TileSizeVertical, const int TilePixelsHorizontal, const int TilePixelsVertical, const bool AddMargin, const TArray<bool>& TilesValidityFlags)
{
// Check all input data
if (File.IsEmpty() || !PFMOrigin || TilesHorizontal < 1 || TilesVertical < 1 || TileSizeHorizontal < 1.f || TileSizeVertical < 1.f || TilePixelsHorizontal < 2 || TilePixelsVertical < 2 || TilesValidityFlags.Num() != TilesHorizontal * TilesVertical)
{
UE_LOG(LogWarpUtilsBlueprint, Error, TEXT("Wrong input data"));
return false;
}
// Explicit XYZ set to NAN instead of FVector(NAN, NAN, NAN) allows to avoid any FVector internal NAN checks
FVector NanVector;
NanVector.X = NanVector.Y = NanVector.Z = NAN;
// Texture XY size
const int TexSizeX = TilesHorizontal * TilePixelsHorizontal;
const int TexSizeY = TilesVertical * TilePixelsVertical;
// Prepare array for output data
TArray<FVector> PFMData;
PFMData.Reserve(TexSizeX * TexSizeY);
// Some constants for navigation math
const FTransform StartFrom = FTransform(StartRotation, StartLocation);
const FVector RowTranslate(0.f, 0.f, -TileSizeVertical);
const FVector ColTranslate(0.f, TileSizeHorizontal, 0.f);
// Compute horizontal margins and pixel offsets
const float PixelOffsetX = (AddMargin ? TileSizeHorizontal / TilePixelsHorizontal : TileSizeHorizontal / (TilePixelsHorizontal - 1));
const float MarginX = (AddMargin ? PixelOffsetX / 2.f : 0.f);
// Compute vertical margins and pixel offsets
const float PixelOffsetY = (AddMargin ? TileSizeVertical / TilePixelsVertical : TileSizeVertical / (TilePixelsVertical - 1));
const float MarginY = (AddMargin ? PixelOffsetY / 2.f : 0.f);
// Cache tile transforms so we won't have to compute it for every pixel
TMap<int, TMap<int, FTransform>> TransformsCache;
// The order is from left to right, from top to bottom
for (int Y = 0; Y < TexSizeY; ++Y)
{
// Current row index
const int CurRow = Y / TilePixelsVertical;
// Transform for current row
const FTransform CurRowTransform = (CurRow == 0 ? StartFrom : FTransform(TransformsCache[CurRow - 1][0].Rotator(), TransformsCache[CurRow - 1][0].TransformPosition(RowTranslate)));
// Cache current row transform
TransformsCache.Emplace(CurRow);
TransformsCache[CurRow].Emplace(0, CurRowTransform);
for (int X = 0; X < TexSizeX; ++X)
{
// Current column index
const int CurCol = X / TilePixelsHorizontal;
// Check if the column transform has been cached previously
const bool bCached = TransformsCache[CurRow].Contains(CurCol);
// Transform of the current tile (top left corner)
const FTransform CurTileTransform = (bCached ?
TransformsCache[CurRow][CurCol] :
(CurCol == 0 ? CurRowTransform : FTransform(TransformsCache[CurRow][CurCol - 1].Rotator().Add(0.f, ColumnAngle, 0.f), TransformsCache[CurRow][CurCol - 1].TransformPosition(ColTranslate))));
// Cache new transform
if (!bCached)
{
TransformsCache[CurRow].Emplace(CurCol, CurTileTransform);
}
// XY within a tile
const int TilePixelY = Y % TilePixelsVertical;
const int TilePixelX = X % TilePixelsHorizontal;
// Tile index in the ValidityFlags array
const int TileArrayIdx = CurCol * TilesVertical + CurRow;
// Fake tiles produce Nan values
if (TilesValidityFlags[TileArrayIdx] == false)
{
PFMData.Add(NanVector);
continue;
}
// Pixel offset in tile space
const FVector TileSpaceOffset = FVector(0.f, MarginX + TilePixelX * PixelOffsetX, -(MarginY + TilePixelY * PixelOffsetY));
// Pixel in world space
const FVector WorldSpacePixel = CurTileTransform.TransformPosition(TileSpaceOffset);
// Pixel in the PFM origin space
const FVector PFMSpacePixel = PFMOrigin->GetActorTransform().InverseTransformPosition(WorldSpacePixel);
// Store current pixel data
PFMData.Add(PFMSpacePixel);
}
}
// Save generated data to file
return SavePFM(File, TexSizeX, TexSizeY, PFMData);
}
@@ -0,0 +1,17 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "WarpUtils.h"
#define LOCTEXT_NAMESPACE "FWarpUtilsModule"
void FWarpUtilsModule::StartupModule()
{
}
void FWarpUtilsModule::ShutdownModule()
{
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FWarpUtilsModule, WarpUtils)
@@ -0,0 +1,6 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "WarpUtilsLog.h"
// Plugin-wide log categories
DEFINE_LOG_CATEGORY(LogWarpUtilsBlueprint);
@@ -0,0 +1,7 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
DECLARE_LOG_CATEGORY_EXTERN(LogWarpUtilsBlueprint, Log, All);
@@ -0,0 +1,42 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "WarpUtilsBlueprintLibrary.generated.h"
UCLASS()
class UWarpUtilsBlueprintLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
// Save data to a PFM file
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Save PFM"), Category = "Miscellaneous|Warp")
static bool SavePFM(const FString& File, const int TexWidth, const int TexHeight, const TArray<FVector>& Vertices);
// Save data to a PFM file. Since the float NaN value is not available in blueprints, we provide a flags array (false == NaN)
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Save PFM Extended"), Category = "Miscellaneous|Warp")
static bool SavePFMEx(const FString& File, const int TexWidth, const int TexHeight, const TArray<FVector>& Vertices, const TArray<bool>& TilesValidityFlags);
// Generate and save data to a PFM file
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate PFM"), Category = "Miscellaneous|Warp")
static bool GeneratePFM(
const FString& File,
const FVector& StartLocation, const FRotator& StartRotation, const AActor* PFMOrigin,
const int TilesHorizontal, const int TilesVertical, const float ColumnAngle,
const float TileSizeHorizontal, const float TileSizeVertical, const int TilePixelsHorizontal, const int TilePixelsVertical, const bool AddMargin
);
// Generate and save data to a PFM file. Additionally, we have an array of tiles validiy flags (false == all pixels of a tile are NaN)
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate PFM Extended"), Category = "Miscellaneous|Warp")
static bool GeneratePFMEx(
const FString& File,
const FVector& StartLocation, const FRotator& StartRotation, const AActor* PFMOrigin,
const int TilesHorizontal, const int TilesVertical, const float ColumnAngle,
const float TileSizeHorizontal, const float TileSizeVertical, const int TilePixelsHorizontal, const int TilePixelsVertical, const bool AddMargin,
const TArray<bool>& TilesValidityFlags
);
};

Some files were not shown because too many files have changed in this diff Show More