bmh/FlightSimulation/Plugins/CesiumForUnreal_5.4/Source/CesiumRuntime/Private/CesiumFeaturesMetadataComponent.cpp
2025-02-07 22:52:32 +08:00

2756 lines
102 KiB
C++

// Copyright 2020-2024 CesiumGS, Inc. and Contributors
#include "CesiumFeaturesMetadataComponent.h"
#include "Cesium3DTileset.h"
#include "CesiumEncodedFeaturesMetadata.h"
#include "CesiumEncodedMetadataConversions.h"
#include "CesiumGltfComponent.h"
#include "CesiumGltfPrimitiveComponent.h"
#include "CesiumModelMetadata.h"
#include "CesiumRuntime.h"
#include "UnrealMetadataConversions.h"
#if WITH_EDITOR
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "ComponentReregisterContext.h"
#include "Containers/Map.h"
#include "ContentBrowserModule.h"
#include "Factories/MaterialFunctionMaterialLayerFactory.h"
#include "IContentBrowserSingleton.h"
#include "IMaterialEditor.h"
#include "Materials/Material.h"
#include "Materials/MaterialExpressionAppendVector.h"
#include "Materials/MaterialExpressionCustom.h"
#include "Materials/MaterialExpressionFunctionInput.h"
#include "Materials/MaterialExpressionFunctionOutput.h"
#include "Materials/MaterialExpressionIf.h"
#include "Materials/MaterialExpressionMaterialFunctionCall.h"
#include "Materials/MaterialExpressionPerInstanceCustomData.h"
#include "Materials/MaterialExpressionRound.h"
#include "Materials/MaterialExpressionScalarParameter.h"
#include "Materials/MaterialExpressionSetMaterialAttributes.h"
#include "Materials/MaterialExpressionTextureCoordinate.h"
#include "Materials/MaterialExpressionTextureObjectParameter.h"
#include "Materials/MaterialExpressionTextureProperty.h"
#include "Materials/MaterialExpressionVectorParameter.h"
#include "Misc/PackageName.h"
#include "Modules/ModuleManager.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "UObject/Package.h"
extern UNREALED_API class UEditorEngine* GEditor;
using namespace CesiumEncodedFeaturesMetadata;
static const FString AutogeneratedMessage = "AUTOGENERATED DO NOT EDIT";
namespace {
void AutoFillPropertyTableDescriptions(
TArray<FCesiumPropertyTableDescription>& Descriptions,
const FCesiumModelMetadata& ModelMetadata) {
const TArray<FCesiumPropertyTable>& propertyTables =
UCesiumModelMetadataBlueprintLibrary::GetPropertyTables(ModelMetadata);
for (const auto& propertyTable : propertyTables) {
FString propertyTableName = getNameForPropertyTable(propertyTable);
FCesiumPropertyTableDescription* pDescription =
Descriptions.FindByPredicate(
[&name = propertyTableName](
const FCesiumPropertyTableDescription& existingPropertyTable) {
return existingPropertyTable.Name == name;
});
if (!pDescription) {
pDescription = &Descriptions.Emplace_GetRef();
pDescription->Name = propertyTableName;
}
const TMap<FString, FCesiumPropertyTableProperty>& properties =
UCesiumPropertyTableBlueprintLibrary::GetProperties(propertyTable);
for (const auto& propertyIt : properties) {
auto pExistingProperty = pDescription->Properties.FindByPredicate(
[&propertyName = propertyIt.Key](
const FCesiumPropertyTablePropertyDescription& existingProperty) {
return existingProperty.Name == propertyName;
});
if (pExistingProperty) {
// We have already accounted for this property, but we may need to check
// for its offset / scale, since they can differ from the class
// property's definition.
ECesiumMetadataType type = pExistingProperty->PropertyDetails.Type;
switch (type) {
case ECesiumMetadataType::Scalar:
case ECesiumMetadataType::Vec2:
case ECesiumMetadataType::Vec3:
case ECesiumMetadataType::Vec4:
case ECesiumMetadataType::Mat2:
case ECesiumMetadataType::Mat3:
case ECesiumMetadataType::Mat4:
break;
default:
continue;
}
FCesiumMetadataValue offset =
UCesiumPropertyTablePropertyBlueprintLibrary::GetOffset(
propertyIt.Value);
pExistingProperty->PropertyDetails.bHasOffset |=
!UCesiumMetadataValueBlueprintLibrary::IsEmpty(offset);
FCesiumMetadataValue scale =
UCesiumPropertyTablePropertyBlueprintLibrary::GetOffset(
propertyIt.Value);
pExistingProperty->PropertyDetails.bHasScale |=
!UCesiumMetadataValueBlueprintLibrary::IsEmpty(scale);
continue;
}
FCesiumPropertyTablePropertyDescription& property =
pDescription->Properties.Emplace_GetRef();
property.Name = propertyIt.Key;
const FCesiumMetadataValueType ValueType =
UCesiumPropertyTablePropertyBlueprintLibrary::GetValueType(
propertyIt.Value);
property.PropertyDetails.SetValueType(ValueType);
property.PropertyDetails.ArraySize =
UCesiumPropertyTablePropertyBlueprintLibrary::GetArraySize(
propertyIt.Value);
property.PropertyDetails.bIsNormalized =
UCesiumPropertyTablePropertyBlueprintLibrary::IsNormalized(
propertyIt.Value);
FCesiumMetadataValue offset =
UCesiumPropertyTablePropertyBlueprintLibrary::GetOffset(
propertyIt.Value);
property.PropertyDetails.bHasOffset =
!UCesiumMetadataValueBlueprintLibrary::IsEmpty(offset);
FCesiumMetadataValue scale =
UCesiumPropertyTablePropertyBlueprintLibrary::GetOffset(
propertyIt.Value);
property.PropertyDetails.bHasScale =
!UCesiumMetadataValueBlueprintLibrary::IsEmpty(scale);
FCesiumMetadataValue noData =
UCesiumPropertyTablePropertyBlueprintLibrary::GetNoDataValue(
propertyIt.Value);
property.PropertyDetails.bHasNoDataValue =
!UCesiumMetadataValueBlueprintLibrary::IsEmpty(noData);
FCesiumMetadataValue defaultValue =
UCesiumPropertyTablePropertyBlueprintLibrary::GetDefaultValue(
propertyIt.Value);
property.PropertyDetails.bHasDefaultValue =
!UCesiumMetadataValueBlueprintLibrary::IsEmpty(defaultValue);
property.EncodingDetails = CesiumMetadataPropertyDetailsToEncodingDetails(
property.PropertyDetails);
}
}
}
void AutoFillPropertyTextureDescriptions(
TArray<FCesiumPropertyTextureDescription>& Descriptions,
const FCesiumModelMetadata& ModelMetadata) {
const TArray<FCesiumPropertyTexture>& propertyTextures =
UCesiumModelMetadataBlueprintLibrary::GetPropertyTextures(ModelMetadata);
for (const auto& propertyTexture : propertyTextures) {
FString propertyTextureName = getNameForPropertyTexture(propertyTexture);
FCesiumPropertyTextureDescription* pDescription =
Descriptions.FindByPredicate(
[&propertyTextureName =
propertyTextureName](const FCesiumPropertyTextureDescription&
existingPropertyTexture) {
return existingPropertyTexture.Name == propertyTextureName;
});
if (!pDescription) {
pDescription = &Descriptions.Emplace_GetRef();
pDescription->Name = propertyTextureName;
}
const TMap<FString, FCesiumPropertyTextureProperty>& properties =
UCesiumPropertyTextureBlueprintLibrary::GetProperties(propertyTexture);
for (const auto& propertyIt : properties) {
auto pExistingProperty = pDescription->Properties.FindByPredicate(
[&propertyName =
propertyIt.Key](const FCesiumPropertyTexturePropertyDescription&
existingProperty) {
return propertyName == existingProperty.Name;
});
if (pExistingProperty) {
// We have already accounted for this property, but we may need to check
// for its offset / scale, since they can differ from the class
// property's definition.
ECesiumMetadataType type = pExistingProperty->PropertyDetails.Type;
switch (type) {
case ECesiumMetadataType::Scalar:
case ECesiumMetadataType::Vec2:
case ECesiumMetadataType::Vec3:
case ECesiumMetadataType::Vec4:
case ECesiumMetadataType::Mat2:
case ECesiumMetadataType::Mat3:
case ECesiumMetadataType::Mat4:
break;
default:
continue;
}
FCesiumMetadataValue offset =
UCesiumPropertyTexturePropertyBlueprintLibrary::GetOffset(
propertyIt.Value);
pExistingProperty->PropertyDetails.bHasOffset |=
!UCesiumMetadataValueBlueprintLibrary::IsEmpty(offset);
FCesiumMetadataValue scale =
UCesiumPropertyTexturePropertyBlueprintLibrary::GetOffset(
propertyIt.Value);
pExistingProperty->PropertyDetails.bHasScale |=
!UCesiumMetadataValueBlueprintLibrary::IsEmpty(scale);
continue;
}
FCesiumPropertyTexturePropertyDescription& property =
pDescription->Properties.Emplace_GetRef();
property.Name = propertyIt.Key;
const FCesiumMetadataValueType ValueType =
UCesiumPropertyTexturePropertyBlueprintLibrary::GetValueType(
propertyIt.Value);
property.PropertyDetails.SetValueType(ValueType);
property.PropertyDetails.ArraySize =
UCesiumPropertyTexturePropertyBlueprintLibrary::GetArraySize(
propertyIt.Value);
property.PropertyDetails.bIsNormalized =
UCesiumPropertyTexturePropertyBlueprintLibrary::IsNormalized(
propertyIt.Value);
FCesiumMetadataValue offset =
UCesiumPropertyTexturePropertyBlueprintLibrary::GetOffset(
propertyIt.Value);
property.PropertyDetails.bHasOffset =
!UCesiumMetadataValueBlueprintLibrary::IsEmpty(offset);
FCesiumMetadataValue scale =
UCesiumPropertyTexturePropertyBlueprintLibrary::GetOffset(
propertyIt.Value);
property.PropertyDetails.bHasScale =
!UCesiumMetadataValueBlueprintLibrary::IsEmpty(scale);
FCesiumMetadataValue noData =
UCesiumPropertyTexturePropertyBlueprintLibrary::GetNoDataValue(
propertyIt.Value);
property.PropertyDetails.bHasNoDataValue =
!UCesiumMetadataValueBlueprintLibrary::IsEmpty(noData);
FCesiumMetadataValue defaultValue =
UCesiumPropertyTexturePropertyBlueprintLibrary::GetDefaultValue(
propertyIt.Value);
property.PropertyDetails.bHasDefaultValue =
!UCesiumMetadataValueBlueprintLibrary::IsEmpty(defaultValue);
auto maybeTextureTransform = propertyIt.Value.getTextureTransform();
if (maybeTextureTransform) {
property.bHasKhrTextureTransform =
(maybeTextureTransform->status() ==
CesiumGltf::KhrTextureTransformStatus::Valid);
}
}
}
}
void AutoFillFeatureIdSetDescriptions(
TArray<FCesiumFeatureIdSetDescription>& Descriptions,
const FCesiumPrimitiveFeatures& Features,
const FCesiumPrimitiveFeatures* InstanceFeatures,
const TArray<FCesiumPropertyTable>& PropertyTables) {
TArray<FCesiumFeatureIdSet> featureIDSets =
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSets(Features);
if (InstanceFeatures) {
featureIDSets.Append(
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSets(
*InstanceFeatures));
}
int32 featureIDTextureCounter = 0;
for (const FCesiumFeatureIdSet& featureIDSet : featureIDSets) {
ECesiumFeatureIdSetType type =
UCesiumFeatureIdSetBlueprintLibrary::GetFeatureIDSetType(featureIDSet);
int64 count =
UCesiumFeatureIdSetBlueprintLibrary::GetFeatureCount(featureIDSet);
if (type == ECesiumFeatureIdSetType::None || count == 0) {
// Empty or invalid feature ID set. Skip.
continue;
}
FString featureIDSetName =
getNameForFeatureIDSet(featureIDSet, featureIDTextureCounter);
FCesiumFeatureIdSetDescription* pDescription = Descriptions.FindByPredicate(
[&name = featureIDSetName](
const FCesiumFeatureIdSetDescription& existingFeatureIDSet) {
return existingFeatureIDSet.Name == name;
});
if (pDescription) {
// We have already accounted for a feature ID set of this name; skip.
continue;
}
pDescription = &Descriptions.Emplace_GetRef();
pDescription->Name = featureIDSetName;
pDescription->Type = type;
const int64 propertyTableIndex =
UCesiumFeatureIdSetBlueprintLibrary::GetPropertyTableIndex(
featureIDSet);
if (propertyTableIndex >= 0 && propertyTableIndex < PropertyTables.Num()) {
const FCesiumPropertyTable& propertyTable =
PropertyTables[propertyTableIndex];
pDescription->PropertyTableName = getNameForPropertyTable(propertyTable);
}
pDescription->bHasNullFeatureId =
UCesiumFeatureIdSetBlueprintLibrary::GetNullFeatureID(featureIDSet) >
-1;
if (type == ECesiumFeatureIdSetType::Texture) {
FCesiumFeatureIdTexture featureIdTexture =
UCesiumFeatureIdSetBlueprintLibrary::GetAsFeatureIDTexture(
featureIDSet);
auto maybeTextureTransform =
featureIdTexture.getFeatureIdTextureView().getTextureTransform();
if (maybeTextureTransform) {
pDescription->bHasKhrTextureTransform =
(maybeTextureTransform->status() ==
CesiumGltf::KhrTextureTransformStatus::Valid);
}
}
}
}
void AutoFillPropertyTextureNames(
TSet<FString>& Names,
const FCesiumPrimitiveMetadata& PrimitiveMetadata,
const TArray<FCesiumPropertyTexture>& PropertyTextures) {
const TArray<int64> propertyTextureIndices =
UCesiumPrimitiveMetadataBlueprintLibrary::GetPropertyTextureIndices(
PrimitiveMetadata);
for (const int64& propertyTextureIndex : propertyTextureIndices) {
if (propertyTextureIndex < 0 ||
propertyTextureIndex >= PropertyTextures.Num()) {
continue;
}
const FCesiumPropertyTexture& propertyTexture =
PropertyTextures[propertyTextureIndex];
FString propertyTextureName = getNameForPropertyTexture(propertyTexture);
Names.Emplace(propertyTextureName);
}
}
} // namespace
void UCesiumFeaturesMetadataComponent::AutoFill() {
const ACesium3DTileset* pOwner = this->GetOwner<ACesium3DTileset>();
if (!pOwner) {
return;
}
Super::PreEditChange(NULL);
// This assumes that the property tables are the same across all models in the
// tileset, and that they all have the same schema.
for (const UActorComponent* pComponent : pOwner->GetComponents()) {
const UCesiumGltfComponent* pGltf = Cast<UCesiumGltfComponent>(pComponent);
if (!pGltf) {
continue;
}
const FCesiumModelMetadata& modelMetadata = pGltf->Metadata;
AutoFillPropertyTableDescriptions(this->PropertyTables, modelMetadata);
AutoFillPropertyTextureDescriptions(this->PropertyTextures, modelMetadata);
TArray<USceneComponent*> childComponents;
pGltf->GetChildrenComponents(false, childComponents);
for (const USceneComponent* pChildComponent : childComponents) {
const auto* pCesiumPrimitive = Cast<ICesiumPrimitive>(pChildComponent);
if (!pCesiumPrimitive) {
continue;
}
const CesiumPrimitiveData& primData =
pCesiumPrimitive->getPrimitiveData();
const FCesiumPrimitiveFeatures& primitiveFeatures = primData.Features;
const TArray<FCesiumPropertyTable>& propertyTables =
UCesiumModelMetadataBlueprintLibrary::GetPropertyTables(
modelMetadata);
const FCesiumPrimitiveFeatures* pInstanceFeatures = nullptr;
const auto* pInstancedComponent =
Cast<UCesiumGltfInstancedComponent>(pChildComponent);
if (pInstancedComponent) {
pInstanceFeatures = pInstancedComponent->pInstanceFeatures.Get();
}
AutoFillFeatureIdSetDescriptions(
this->FeatureIdSets,
primitiveFeatures,
pInstanceFeatures,
propertyTables);
const FCesiumPrimitiveMetadata& primitiveMetadata = primData.Metadata;
const TArray<FCesiumPropertyTexture>& propertyTextures =
UCesiumModelMetadataBlueprintLibrary::GetPropertyTextures(
modelMetadata);
AutoFillPropertyTextureNames(
this->PropertyTextureNames,
primitiveMetadata,
propertyTextures);
}
}
Super::PostEditChange();
}
template <typename ObjClass>
static FORCEINLINE ObjClass* LoadObjFromPath(const FName& Path) {
if (Path == NAME_None)
return nullptr;
return Cast<ObjClass>(
StaticLoadObject(ObjClass::StaticClass(), nullptr, *Path.ToString()));
}
static FORCEINLINE UMaterialFunction* LoadMaterialFunction(const FName& Path) {
if (Path == NAME_None)
return nullptr;
return LoadObjFromPath<UMaterialFunction>(Path);
}
static const FString GetPropertyValuesPrefix = "Get Property Values From ";
static const FString ApplyValueTransformsPrefix = "Apply Value Transforms To ";
namespace {
struct MaterialNodeClassification {
TArray<UMaterialExpression*> AutoGeneratedNodes;
TArray<UMaterialExpressionMaterialFunctionCall*> GetFeatureIdNodes;
TArray<UMaterialExpressionCustom*> GetPropertyValueNodes;
TArray<UMaterialExpressionCustom*> ApplyValueTransformNodes;
TArray<UMaterialExpressionIf*> IfNodes;
TArray<UMaterialExpression*> UserAddedNodes;
};
struct MaterialFunctionLibrary {
UMaterialFunction* SelectTexCoords = nullptr;
UMaterialFunction* TransformTexCoords = nullptr;
UMaterialFunction* GetFeatureIdsFromAttribute = nullptr;
UMaterialFunction* GetFeatureIdsFromTexture = nullptr;
UMaterialFunction* GetFeatureIdsFromInstance = nullptr;
MaterialFunctionLibrary()
: SelectTexCoords(LoadMaterialFunction(
"/CesiumForUnreal/Materials/MaterialFunctions/CesiumSelectTexCoords.CesiumSelectTexCoords")),
TransformTexCoords(LoadMaterialFunction(
"/CesiumForUnreal/Materials/MaterialFunctions/MF_CesiumTransformTextureCoordinates.MF_CesiumTransformTextureCoordinates")),
GetFeatureIdsFromAttribute(LoadMaterialFunction(
"/CesiumForUnreal/Materials/MaterialFunctions/CesiumGetFeatureIdsFromAttribute.CesiumGetFeatureIdsFromAttribute")),
GetFeatureIdsFromTexture(LoadMaterialFunction(
"/CesiumForUnreal/Materials/MaterialFunctions/CesiumGetFeatureIdsFromTexture.CesiumGetFeatureIdsFromTexture")),
GetFeatureIdsFromInstance(LoadMaterialFunction(
"/CesiumForUnreal/Materials/MaterialFunctions/CesiumGetFeatureIdsFromInstance.CesiumGetFeatureIdsFromInstance")) {
}
bool isValid() {
return SelectTexCoords != nullptr &&
GetFeatureIdsFromAttribute != nullptr &&
GetFeatureIdsFromTexture != nullptr &&
GetFeatureIdsFromInstance != nullptr;
}
};
} // namespace
// Separate nodes into auto-generated and user-added. Collect the property
// result nodes.
static void ClassifyNodes(
UMaterialFunctionMaterialLayer* Layer,
MaterialNodeClassification& Classification,
const MaterialFunctionLibrary& FunctionLibrary) {
const UMaterialFunction* GetFeatureIdsFromAttributeFunction =
FunctionLibrary.GetFeatureIdsFromAttribute;
const UMaterialFunction* GetFeatureIdsFromTextureFunction =
FunctionLibrary.GetFeatureIdsFromTexture;
const UMaterialFunction* GetFeatureIdsFromInstanceFunction =
FunctionLibrary.GetFeatureIdsFromInstance;
for (const TObjectPtr<UMaterialExpression>& Node :
Layer->GetExpressionCollection().Expressions) {
// Check if this node is marked as autogenerated.
if (Node->Desc.StartsWith(
AutogeneratedMessage,
ESearchCase::Type::CaseSensitive)) {
Classification.AutoGeneratedNodes.Add(Node);
UMaterialExpressionCustom* CustomNode =
Cast<UMaterialExpressionCustom>(Node);
if (CustomNode &&
CustomNode->Description.Contains(GetPropertyValuesPrefix)) {
Classification.GetPropertyValueNodes.Add(CustomNode);
continue;
}
if (CustomNode &&
CustomNode->Description.Contains(ApplyValueTransformsPrefix)) {
Classification.ApplyValueTransformNodes.Add(CustomNode);
continue;
}
// If nodes are added in when feature ID sets specify a null feature ID
// value, when properties specify a "no data" value, and when properties
// specify a default value.
UMaterialExpressionIf* IfNode = Cast<UMaterialExpressionIf>(Node);
if (IfNode) {
Classification.IfNodes.Add(IfNode);
continue;
}
UMaterialExpressionMaterialFunctionCall* FunctionCallNode =
Cast<UMaterialExpressionMaterialFunctionCall>(Node);
if (!FunctionCallNode)
continue;
const FName& name = FunctionCallNode->MaterialFunction->GetFName();
if (name == GetFeatureIdsFromAttributeFunction->GetFName() ||
name == GetFeatureIdsFromTextureFunction->GetFName() ||
name == GetFeatureIdsFromInstanceFunction->GetFName()) {
Classification.GetFeatureIdNodes.Add(FunctionCallNode);
}
} else {
Classification.UserAddedNodes.Add(Node);
}
}
}
static void ClearAutoGeneratedNodes(
UMaterialFunctionMaterialLayer* Layer,
TMap<FString, TMap<FString, const FExpressionInput*>>& ConnectionInputRemap,
TMap<FString, TArray<FExpressionInput*>>& ConnectionOutputRemap,
const MaterialFunctionLibrary& FunctionLibrary) {
MaterialNodeClassification Classification;
ClassifyNodes(Layer, Classification, FunctionLibrary);
// Determine which user-added connections to remap when regenerating the
// feature ID retrieval nodes.
for (const UMaterialExpressionMaterialFunctionCall* GetFeatureIdNode :
Classification.GetFeatureIdNodes) {
if (!GetFeatureIdNode->Outputs.Num()) {
continue;
}
const auto Inputs = GetFeatureIdNode->FunctionInputs;
if (!Inputs.Num()) {
// Should not happen, but just in case, this node would be invalid. Break
// any user-made connections to this node and don't attempt to remap it.
for (UMaterialExpression* UserNode : Classification.UserAddedNodes) {
for (FExpressionInput* Input : UserNode->GetInputsView()) {
if (Input->Expression == GetFeatureIdNode &&
Input->OutputIndex == 0) {
Input->Expression = nullptr;
}
}
}
continue;
}
// It's not as easy to distinguish the material function calls from each
// other. Try using the name of the first valid input (the texture
// coordinate index name), which should be different for each feature ID
// set.
const auto Parameter =
Cast<UMaterialExpressionParameter>(Inputs[0].Input.Expression);
FString ParameterName;
if (Parameter) {
ParameterName = Parameter->ParameterName.ToString();
}
if (ParameterName.IsEmpty()) {
// In case, treat the node as invalid. Break any user-made connections to
// this node and don't attempt to remap it.
for (UMaterialExpression* UserNode : Classification.UserAddedNodes) {
for (FExpressionInput* Input : UserNode->GetInputsView()) {
if (Input->Expression == GetFeatureIdNode &&
Input->OutputIndex == 0) {
Input->Expression = nullptr;
}
}
}
continue;
}
FString Key = GetFeatureIdNode->GetDescription() + ParameterName;
TArray<FExpressionInput*> Connections;
for (UMaterialExpression* UserNode : Classification.UserAddedNodes) {
for (FExpressionInput* Input : UserNode->GetInputsView()) {
// Look for user-made connections to this node.
if (Input->Expression == GetFeatureIdNode && Input->OutputIndex == 0) {
Connections.Add(Input);
Input->Expression = nullptr;
}
}
}
ConnectionOutputRemap.Emplace(MoveTemp(Key), MoveTemp(Connections));
}
// Determine which user-added connections to remap when regenerating the
// property value retrieval nodes.
for (const UMaterialExpressionCustom* GetPropertyValueNode :
Classification.GetPropertyValueNodes) {
int32 OutputIndex = 0;
for (const FExpressionOutput& PropertyOutput :
GetPropertyValueNode->Outputs) {
FString Key = GetPropertyValueNode->GetDescription() +
PropertyOutput.OutputName.ToString();
// Look for user-made connections to this property.
TArray<FExpressionInput*> Connections;
for (UMaterialExpression* UserNode : Classification.UserAddedNodes) {
for (FExpressionInput* Input : UserNode->GetInputsView()) {
if (Input->Expression == GetPropertyValueNode &&
Input->OutputIndex == OutputIndex) {
Connections.Add(Input);
Input->Expression = nullptr;
}
}
}
ConnectionOutputRemap.Emplace(MoveTemp(Key), MoveTemp(Connections));
++OutputIndex;
}
}
// Determine which user-added connections to remap when regenerating the
// value transform nodes.
for (const UMaterialExpressionCustom* ApplyValueTransformNode :
Classification.ApplyValueTransformNodes) {
int32 OutputIndex = 0;
for (const FExpressionOutput& PropertyOutput :
ApplyValueTransformNode->Outputs) {
FString Key = ApplyValueTransformNode->GetDescription() +
PropertyOutput.OutputName.ToString();
// Look for user-made connections to this property.
TArray<FExpressionInput*> Connections;
for (UMaterialExpression* UserNode : Classification.UserAddedNodes) {
for (FExpressionInput* Input : UserNode->GetInputsView()) {
if (Input->Expression == ApplyValueTransformNode &&
Input->OutputIndex == OutputIndex) {
Connections.Add(Input);
Input->Expression = nullptr;
}
}
}
ConnectionOutputRemap.Emplace(MoveTemp(Key), MoveTemp(Connections));
++OutputIndex;
}
}
// Determine which user-added connections to remap when regenerating the if
// statements for null feature IDs.
for (const UMaterialExpressionIf* IfNode : Classification.IfNodes) {
// Distinguish the if statements from each other using A and B. If both of
// these nodes have been disconnected, then treat this node as invalid.
FString IfNodeName;
UMaterialExpressionParameter* Parameter =
Cast<UMaterialExpressionParameter>(IfNode->A.Expression);
if (Parameter) {
IfNodeName += Parameter->GetParameterName().ToString();
} else if (IfNode->A.Expression) {
TArray<FExpressionOutput>& Outputs = IfNode->A.Expression->GetOutputs();
if (Outputs.Num() > 0) {
IfNodeName += Outputs[IfNode->A.OutputIndex].OutputName.ToString();
}
}
Parameter = Cast<UMaterialExpressionParameter>(IfNode->B.Expression);
if (Parameter) {
IfNodeName += Parameter->GetParameterName().ToString();
} else if (IfNode->B.Expression) {
TArray<FExpressionOutput>& Outputs = IfNode->B.Expression->GetOutputs();
if (Outputs.Num() > 0) {
IfNodeName += Outputs[IfNode->B.OutputIndex].OutputName.ToString();
}
}
if (IfNodeName.IsEmpty()) {
// In case, treat the node as invalid. Break any user-made connections to
// this node and don't attempt to remap it.
for (UMaterialExpression* UserNode : Classification.UserAddedNodes) {
for (FExpressionInput* Input : UserNode->GetInputsView()) {
if (Input->Expression == IfNode && Input->OutputIndex == 0) {
Input->Expression = nullptr;
}
}
}
continue;
}
FString Key = IfNode->GetDescription() + IfNodeName;
TArray<FExpressionInput*> Connections;
for (UMaterialExpression* UserNode : Classification.UserAddedNodes) {
for (FExpressionInput* Input : UserNode->GetInputsView()) {
// Look for user-made connections to this node.
if (Input->Expression == IfNode && Input->OutputIndex == 0) {
Connections.Add(Input);
Input->Expression = nullptr;
}
}
}
ConnectionOutputRemap.Emplace(Key, MoveTemp(Connections));
// Also save any user inputs to the if statement. Be sure to ignore
// connections to autogenerated nodes.
TMap<FString, const FExpressionInput*> InputConnections;
if (IfNode->AGreaterThanB.Expression &&
!IfNode->AGreaterThanB.Expression->Desc.StartsWith(
AutogeneratedMessage,
ESearchCase::Type::CaseSensitive)) {
InputConnections.Emplace(TEXT("AGreaterThanB"), &IfNode->AGreaterThanB);
}
if (IfNode->ALessThanB.Expression &&
!IfNode->ALessThanB.Expression->Desc.StartsWith(
AutogeneratedMessage,
ESearchCase::Type::CaseSensitive)) {
InputConnections.Emplace(TEXT("ALessThanB"), &IfNode->ALessThanB);
}
if (IfNode->AEqualsB.Expression &&
!IfNode->AEqualsB.Expression->Desc.StartsWith(
AutogeneratedMessage,
ESearchCase::Type::CaseSensitive)) {
InputConnections.Emplace(TEXT("AEqualsB"), &IfNode->AEqualsB);
}
ConnectionInputRemap.Emplace(MoveTemp(Key), MoveTemp(InputConnections));
}
// Remove auto-generated nodes.
for (UMaterialExpression* AutoGeneratedNode :
Classification.AutoGeneratedNodes) {
Layer->GetExpressionCollection().RemoveExpression(AutoGeneratedNode);
}
}
static void RemapUserConnections(
UMaterialFunctionMaterialLayer* Layer,
TMap<FString, TMap<FString, const FExpressionInput*>>& ConnectionInputRemap,
TMap<FString, TArray<FExpressionInput*>>& ConnectionOutputRemap,
const MaterialFunctionLibrary& FunctionLibrary) {
MaterialNodeClassification Classification;
ClassifyNodes(Layer, Classification, FunctionLibrary);
for (UMaterialExpressionMaterialFunctionCall* GetFeatureIdNode :
Classification.GetFeatureIdNodes) {
const auto Inputs = GetFeatureIdNode->FunctionInputs;
if (!Inputs.IsEmpty()) {
const auto Parameter =
Cast<UMaterialExpressionParameter>(Inputs[0].Input.Expression);
FString ParameterName = Parameter->ParameterName.ToString();
FString Key = GetFeatureIdNode->GetDescription() + ParameterName;
TArray<FExpressionInput*>* pConnections = ConnectionOutputRemap.Find(Key);
if (pConnections) {
for (FExpressionInput* pConnection : *pConnections) {
pConnection->Connect(0, GetFeatureIdNode);
}
}
}
}
for (UMaterialExpressionCustom* GetPropertyValueNode :
Classification.GetPropertyValueNodes) {
int32 OutputIndex = 0;
for (const FExpressionOutput& PropertyOutput :
GetPropertyValueNode->Outputs) {
FString Key = GetPropertyValueNode->Description +
PropertyOutput.OutputName.ToString();
TArray<FExpressionInput*>* pConnections = ConnectionOutputRemap.Find(Key);
if (pConnections) {
for (FExpressionInput* pConnection : *pConnections) {
pConnection->Connect(OutputIndex, GetPropertyValueNode);
}
}
++OutputIndex;
}
}
for (UMaterialExpressionCustom* ApplyValueTransformNode :
Classification.ApplyValueTransformNodes) {
int32 OutputIndex = 0;
for (const FExpressionOutput& PropertyOutput :
ApplyValueTransformNode->Outputs) {
FString Key = ApplyValueTransformNode->Description +
PropertyOutput.OutputName.ToString();
TArray<FExpressionInput*>* pConnections = ConnectionOutputRemap.Find(Key);
if (pConnections) {
for (FExpressionInput* pConnection : *pConnections) {
pConnection->Connect(OutputIndex, ApplyValueTransformNode);
}
}
++OutputIndex;
}
}
for (UMaterialExpressionIf* IfNode : Classification.IfNodes) {
FString IfNodeName;
FString AName;
UMaterialExpressionParameter* Parameter =
Cast<UMaterialExpressionParameter>(IfNode->A.Expression);
if (Parameter) {
AName = Parameter->GetParameterName().ToString();
} else if (IfNode->A.Expression) {
TArray<FExpressionOutput>& Outputs = IfNode->A.Expression->GetOutputs();
if (Outputs.Num() > 0) {
AName = Outputs[IfNode->A.OutputIndex].OutputName.ToString();
}
}
FString BName;
Parameter = Cast<UMaterialExpressionParameter>(IfNode->B.Expression);
if (Parameter) {
BName = Parameter->GetParameterName().ToString();
} else if (IfNode->B.Expression) {
TArray<FExpressionOutput>& Outputs = IfNode->B.Expression->GetOutputs();
if (Outputs.Num() > 0) {
BName = Outputs[IfNode->B.OutputIndex].OutputName.ToString();
}
}
IfNodeName = AName + BName;
FString Key = IfNode->GetDescription() + IfNodeName;
TArray<FExpressionInput*>* pConnections = ConnectionOutputRemap.Find(Key);
if (pConnections) {
for (FExpressionInput* pConnection : *pConnections) {
pConnection->Connect(0, IfNode);
}
}
if (AName.Contains(MaterialPropertyHasValueSuffix)) {
// Skip the if statement that handles omitted properties. All connections
// to this node are meant to be autogenerated.
continue;
}
bool isNoDataIfStatement = BName.Contains("NoData");
TMap<FString, const FExpressionInput*>* pInputConnections =
ConnectionInputRemap.Find(Key);
if (pInputConnections) {
const FExpressionInput** ppAGreaterThanB =
pInputConnections->Find(TEXT("AGreaterThanB"));
if (ppAGreaterThanB && *ppAGreaterThanB) {
IfNode->AGreaterThanB = **ppAGreaterThanB;
}
const FExpressionInput** ppALessThanB =
pInputConnections->Find(TEXT("ALessThanB"));
if (ppALessThanB && *ppALessThanB) {
IfNode->ALessThanB = **ppALessThanB;
}
if (isNoDataIfStatement && IfNode->AEqualsB.Expression) {
// If this node is comparing the "no data" value, the property may also
// have a default value. If it does, it will have already been connected
// to this expression; don't overwrite it.
continue;
}
const FExpressionInput** ppAEqualsB =
pInputConnections->Find(TEXT("AEqualsB"));
if (ppAEqualsB && *ppAEqualsB) {
IfNode->AEqualsB = **ppAEqualsB;
}
}
}
}
// Increment constant that is used to space out the autogenerated nodes.
static const int32 Incr = 200;
namespace {
/**
* Computes a scalar for spacing out material nodes. The actual computation is
* rather arbitrary, but this prevents clumping when properties have extremely
* long names.
*/
float GetNameLengthScalar(const FName& Name) {
return FMath::Max(static_cast<float>(Name.GetStringLength()) / 24, 1.0f);
}
float GetNameLengthScalar(const FString& Name) {
return FMath::Max(static_cast<float>(Name.Len()) / 24, 1.0f);
}
ECustomMaterialOutputType
GetOutputTypeForEncodedType(ECesiumEncodedMetadataType Type) {
switch (Type) {
case ECesiumEncodedMetadataType::Vec2:
return ECustomMaterialOutputType::CMOT_Float2;
case ECesiumEncodedMetadataType::Vec3:
return ECustomMaterialOutputType::CMOT_Float3;
case ECesiumEncodedMetadataType::Vec4:
return ECustomMaterialOutputType::CMOT_Float4;
case ECesiumEncodedMetadataType::Scalar:
default:
return ECustomMaterialOutputType::CMOT_Float1;
};
}
FString GetSwizzleForEncodedType(ECesiumEncodedMetadataType Type) {
switch (Type) {
case ECesiumEncodedMetadataType::Scalar:
return ".r";
case ECesiumEncodedMetadataType::Vec2:
return ".rg";
case ECesiumEncodedMetadataType::Vec3:
return ".rgb";
case ECesiumEncodedMetadataType::Vec4:
return ".rgba";
default:
return FString();
};
}
/**
* @brief Generates code for assembling metadata values from a scalar property
* texture property.
*/
FString GenerateCodeForScalarPropertyTextureProperty(
const FString& PropertyName,
const FString& PropertyChannelsName,
const FCesiumMetadataPropertyDetails& PropertyDetails) {
// Example: "heightResult"
FString PropertyResultName = PropertyName + "Result";
// clang-format off
// Example: "uint heightResult = 0;"
FString code = "uint " + PropertyResultName + " = 0;\n";
// clang-format on
FString SampleString = "sample = asuint(f.Get(sampleColor, channel));\n";
uint32 byteSize = GetMetadataTypeByteSize(
PropertyDetails.Type,
PropertyDetails.ComponentType);
if (byteSize == 1) {
// clang-format off
code += "channel = uint(f.Get(" + PropertyChannelsName + ", 0));\n" +
SampleString +
PropertyResultName + " = sample;\n";
// clang-format on
} else {
// clang-format off
FString byteSizeString = std::to_string(byteSize).c_str();
code += "byteOffset = 0;\n"
"for (uint i = 0; i < " + byteSizeString + "; i++) {\n"
" channel = uint(f.Get(" + PropertyChannelsName + ", i));\n" +
" " + SampleString +
" " + PropertyResultName + " = " +
PropertyResultName + " | (sample << byteOffset);\n"
" byteOffset += 8;\n"
"}\n";
// clang-format on
}
FString OutputName = PropertyName;
if (PropertyDetails.bIsNormalized || PropertyDetails.bHasOffset ||
PropertyDetails.bHasScale) {
OutputName += MaterialPropertyRawSuffix;
}
switch (PropertyDetails.ComponentType) {
case ECesiumMetadataComponentType::Float32:
code += OutputName + " = asfloat(" + PropertyResultName + ");\n";
break;
case ECesiumMetadataComponentType::Int8:
case ECesiumMetadataComponentType::Int16:
case ECesiumMetadataComponentType::Int32:
code += OutputName + " = asint(" + PropertyResultName + ");\n";
default:
code += OutputName + " = " + PropertyResultName + ";\n";
break;
}
return code;
}
/**
* @brief Generates code for assembling metadata values from a vec2 property
* texture property.
*/
FString GenerateCodeForVec2PropertyTextureProperty(
const FString& PropertyName,
const FString& PropertyChannelsName,
const FCesiumMetadataPropertyDetails& PropertyDetails) {
FString ComponentString;
switch (PropertyDetails.ComponentType) {
case ECesiumMetadataComponentType::Uint8:
case ECesiumMetadataComponentType::Uint16:
ComponentString = "uint";
break;
case ECesiumMetadataComponentType::Int8:
case ECesiumMetadataComponentType::Int16:
ComponentString = "int";
break;
default:
// Only 1 or 2-byte components are supported.
return FString();
}
// Example: "sample = asuint(f.Get(sampleColor, channel));"
FString SampleString =
"sample = as" + ComponentString + "(f.Get(sampleColor, channel));\n";
// Example: "uint2"
FString TypeString = ComponentString + "2";
// Example: "dimensionsResult"
FString PropertyResultName = PropertyName + "Result";
// Example: "uint2 dimensionsResult = uint2(0,0);"
FString code =
TypeString + " " + PropertyResultName + " = " + TypeString + "(0, 0);\n";
if (GetMetadataTypeByteSize(
PropertyDetails.Type,
PropertyDetails.ComponentType) == 1) {
// clang-format off
code += "channel = uint(f.Get(" + PropertyChannelsName + ", 0));\n" +
SampleString +
PropertyResultName + ".x = sample;\n"
"channel = uint(f.Get(" + PropertyChannelsName + ", 1));\n" +
SampleString +
PropertyResultName + ".y = sample;\n";
// clang-format on
} else {
// clang-format off
code += "channel = uint(f.Get(" + PropertyChannelsName + ", 0));\n" +
SampleString +
PropertyResultName + ".x = sample;\n"
"channel = uint(f.Get(" + PropertyChannelsName + ", 1));\n" +
SampleString +
PropertyResultName + ".x = " + PropertyResultName +
".x | (sample << 8);\n "
"channel = uint(f.Get(" + PropertyChannelsName + ", 2));\n" +
SampleString +
PropertyResultName + ".y = sample;\n"
"channel = uint(f.Get(" + PropertyChannelsName + ", 3));\n" +
SampleString +
PropertyResultName + ".y = " + PropertyResultName +
".y | (sample << 8);\n ";
// clang-format on
}
return code;
}
/**
* @brief Generates code for assembling metadata values from a vecN property
* texture property, assuming it contains single-byte components.
*/
FString GenerateCodeForVecNPropertyTextureProperty(
const FString& PropertyName,
const FString& PropertyChannelsName,
ECesiumMetadataComponentType ComponentType,
uint32 Count) {
FString ComponentString;
// Only single-byte components are supported.
switch (ComponentType) {
case ECesiumMetadataComponentType::Uint8:
ComponentString = "uint";
break;
case ECesiumMetadataComponentType::Int8:
ComponentString = "int";
break;
default:
return FString();
}
FString CountString;
FString ZeroString;
switch (Count) {
case 2:
CountString = "2";
ZeroString = "(0, 0)";
break;
case 3:
CountString = "3";
ZeroString = "(0, 0, 0)";
break;
case 4:
CountString = "4";
ZeroString = "(0, 0, 0, 0)";
break;
default:
return FString();
}
// Example: "uint4"
FString TypeString = ComponentString + CountString;
// Example: "colorResult"
FString PropertyResultName = PropertyName + "Result";
// Example: "sample = asuint(f.Get(sampleColor, channel));"
FString SampleString =
"sample = as" + ComponentString + "(f.Get(sampleColor, channel));\n ";
// Example: "uint4 colorResult = uint4(0, 0, 0, 0);"
// clang-format off
FString code =
TypeString + " " + PropertyResultName + " = " + TypeString + ZeroString +
";\n"
"channel = uint(f.Get(" + PropertyChannelsName + ", 0));\n" +
SampleString +
PropertyResultName + ".x = sample;\n"
"channel = uint(f.Get(" + PropertyChannelsName + ", 1));\n" +
SampleString +
PropertyResultName + ".y = sample;\n";
// clang-format on
if (Count >= 3) {
// clang-format off
code += "channel = uint(f.Get(" + PropertyChannelsName + ", 2));\n" +
SampleString +
PropertyResultName + ".z = sample;\n";
// clang-format on
}
if (Count == 4) {
// clang-format off
code += "channel = uint(f.Get(" + PropertyChannelsName + ", 3));\n" +
SampleString +
PropertyResultName + ".w = sample;\n";
// clang-format on
}
return code;
}
/**
* @brief Generates code for assembling metadata values from a property texture
* property, depending on its type.
*/
FString GenerateCodeForPropertyTextureProperty(
const FString& PropertyName,
const FString& PropertyUVName,
const FString& PropertyDataName,
const FString& PropertyChannelsName,
const FCesiumMetadataPropertyDetails& PropertyDetails) {
// Example: sampleColor = Height_DATA.Sample(Height_DATASampler, Height_UV);
// clang-format off
FString code =
"sampleColor = " +
PropertyDataName + ".Sample(" + PropertyDataName + "Sampler, " + PropertyUVName + ");\n";
// clang-format on
if (PropertyDetails.bIsArray) {
if (GetMetadataTypeByteSize(
PropertyDetails.Type,
PropertyDetails.ComponentType) > 1) {
// Only single-byte array values are supported.
return FString();
}
return code + GenerateCodeForVecNPropertyTextureProperty(
PropertyName,
PropertyChannelsName,
PropertyDetails.ComponentType,
PropertyDetails.ArraySize);
}
switch (PropertyDetails.Type) {
case ECesiumMetadataType::Scalar:
return code + GenerateCodeForScalarPropertyTextureProperty(
PropertyName,
PropertyChannelsName,
PropertyDetails);
case ECesiumMetadataType::Vec2:
// Vec2s must be handled differently because they can consist of either
// single-byte or double-byte components
return code + GenerateCodeForVec2PropertyTextureProperty(
PropertyName,
PropertyChannelsName,
PropertyDetails);
case ECesiumMetadataType::Vec3:
return code + GenerateCodeForVecNPropertyTextureProperty(
PropertyName,
PropertyChannelsName,
PropertyDetails.ComponentType,
3);
case ECesiumMetadataType::Vec4:
return code + GenerateCodeForVecNPropertyTextureProperty(
PropertyName,
PropertyChannelsName,
PropertyDetails.ComponentType,
4);
default:
return FString();
}
}
/**
* @brief Generates the nodes necessary to sample feature IDs from a feature ID
* texture.
*/
UMaterialExpressionMaterialFunctionCall* GenerateNodesForFeatureIdTexture(
const FCesiumFeatureIdSetDescription& Description,
TArray<UMaterialExpression*>& AutoGeneratedNodes,
UMaterialFunctionMaterialLayer* TargetMaterialLayer,
const MaterialFunctionLibrary& FunctionLibrary,
int32& NodeX,
int32& NodeY) {
int32 MaximumParameterSectionX = 0;
FString SafeName = createHlslSafeName(Description.Name);
UMaterialExpressionScalarParameter* TexCoordsIndex =
NewObject<UMaterialExpressionScalarParameter>(TargetMaterialLayer);
TexCoordsIndex->ParameterName = FName(SafeName + MaterialTexCoordIndexSuffix);
TexCoordsIndex->DefaultValue = 0.0f;
TexCoordsIndex->MaterialExpressionEditorX = NodeX;
TexCoordsIndex->MaterialExpressionEditorY = NodeY;
AutoGeneratedNodes.Add(TexCoordsIndex);
MaximumParameterSectionX = FMath::Max(
MaximumParameterSectionX,
Incr * GetNameLengthScalar(TexCoordsIndex->ParameterName));
NodeY += 0.75 * Incr;
UMaterialExpressionTextureObjectParameter* FeatureIdTexture =
NewObject<UMaterialExpressionTextureObjectParameter>(TargetMaterialLayer);
FeatureIdTexture->ParameterName = FName(SafeName + MaterialTextureSuffix);
FeatureIdTexture->MaterialExpressionEditorX = NodeX;
FeatureIdTexture->MaterialExpressionEditorY = NodeY;
AutoGeneratedNodes.Add(FeatureIdTexture);
MaximumParameterSectionX = FMath::Max(
MaximumParameterSectionX,
Incr * GetNameLengthScalar(FeatureIdTexture->ParameterName));
NodeY += Incr;
UMaterialExpressionScalarParameter* NumChannels =
NewObject<UMaterialExpressionScalarParameter>(TargetMaterialLayer);
NumChannels->ParameterName = FName(SafeName + MaterialNumChannelsSuffix);
NumChannels->DefaultValue = 0.0f;
NumChannels->MaterialExpressionEditorX = NodeX;
NumChannels->MaterialExpressionEditorY = NodeY;
AutoGeneratedNodes.Add(NumChannels);
MaximumParameterSectionX = FMath::Max(
MaximumParameterSectionX,
Incr * GetNameLengthScalar(NumChannels->ParameterName));
NodeY += 0.75 * Incr;
UMaterialExpressionVectorParameter* Channels =
NewObject<UMaterialExpressionVectorParameter>(TargetMaterialLayer);
Channels->ParameterName = FName(SafeName + MaterialChannelsSuffix);
Channels->DefaultValue = FLinearColor(0, 0, 0, 0);
Channels->MaterialExpressionEditorX = NodeX;
Channels->MaterialExpressionEditorY = NodeY;
AutoGeneratedNodes.Add(Channels);
// KHR_texture_transform parameters
UMaterialExpressionVectorParameter* TransformScaleOffset = nullptr;
UMaterialExpressionVectorParameter* TransformRotation = nullptr;
if (Description.bHasKhrTextureTransform) {
TransformScaleOffset =
NewObject<UMaterialExpressionVectorParameter>(TargetMaterialLayer);
TransformScaleOffset->ParameterName =
FName(SafeName + MaterialTextureScaleOffsetSuffix);
TransformScaleOffset->DefaultValue = {1, 1, 0, 0};
TransformScaleOffset->MaterialExpressionEditorX = NodeX;
TransformScaleOffset->MaterialExpressionEditorY = NodeY + 1.25 * Incr;
AutoGeneratedNodes.Add(TransformScaleOffset);
MaximumParameterSectionX = FMath::Max(
MaximumParameterSectionX,
Incr * GetNameLengthScalar(TransformScaleOffset->ParameterName));
TransformRotation =
NewObject<UMaterialExpressionVectorParameter>(TargetMaterialLayer);
TransformRotation->ParameterName =
FName(SafeName + MaterialTextureRotationSuffix);
TransformRotation->DefaultValue = {0, 1, 0, 1};
TransformRotation->MaterialExpressionEditorX = NodeX;
TransformRotation->MaterialExpressionEditorY = NodeY + 2.5 * Incr;
AutoGeneratedNodes.Add(TransformRotation);
}
NodeX += MaximumParameterSectionX + Incr;
UMaterialExpressionAppendVector* AppendChannels =
NewObject<UMaterialExpressionAppendVector>(TargetMaterialLayer);
AppendChannels->MaterialExpressionEditorX = NodeX;
AppendChannels->MaterialExpressionEditorY = NodeY;
AppendChannels->A.Connect(0, Channels);
AppendChannels->B.Connect(4, Channels);
AutoGeneratedNodes.Add(AppendChannels);
UMaterialExpressionAppendVector* AppendScaleOffset = nullptr;
if (Description.bHasKhrTextureTransform) {
AppendScaleOffset =
NewObject<UMaterialExpressionAppendVector>(TargetMaterialLayer);
AppendScaleOffset->MaterialExpressionEditorX = NodeX;
AppendScaleOffset->MaterialExpressionEditorY =
TransformScaleOffset->MaterialExpressionEditorY;
AppendScaleOffset->A.Connect(0, TransformScaleOffset);
AppendScaleOffset->B.Connect(4, TransformScaleOffset);
AutoGeneratedNodes.Add(AppendScaleOffset);
}
NodeY -= 1.75 * Incr;
NodeX += 1.25 * Incr;
UMaterialExpressionMaterialFunctionCall* GetFeatureIdsFromTexture =
NewObject<UMaterialExpressionMaterialFunctionCall>(TargetMaterialLayer);
GetFeatureIdsFromTexture->MaterialFunction =
FunctionLibrary.GetFeatureIdsFromTexture;
GetFeatureIdsFromTexture->MaterialExpressionEditorX = NodeX;
GetFeatureIdsFromTexture->MaterialExpressionEditorY = NodeY;
FunctionLibrary.GetFeatureIdsFromTexture->GetInputsAndOutputs(
GetFeatureIdsFromTexture->FunctionInputs,
GetFeatureIdsFromTexture->FunctionOutputs);
GetFeatureIdsFromTexture->FunctionInputs[0].Input.Expression = TexCoordsIndex;
GetFeatureIdsFromTexture->FunctionInputs[1].Input.Expression =
FeatureIdTexture;
GetFeatureIdsFromTexture->FunctionInputs[2].Input.Expression = NumChannels;
GetFeatureIdsFromTexture->FunctionInputs[3].Input.Expression = AppendChannels;
if (Description.bHasKhrTextureTransform) {
GetFeatureIdsFromTexture->FunctionInputs[4].Input.Connect(
0,
AppendScaleOffset);
GetFeatureIdsFromTexture->FunctionInputs[5].Input.Connect(
0,
TransformRotation);
}
AutoGeneratedNodes.Add(GetFeatureIdsFromTexture);
NodeX += 2 * Incr;
return GetFeatureIdsFromTexture;
}
/**
* @brief Generates the nodes necessary to sample feature IDs from a feature ID
* attribute.
*/
UMaterialExpressionMaterialFunctionCall* GenerateNodesForFeatureIdAttribute(
const FCesiumFeatureIdSetDescription& Description,
TArray<UMaterialExpression*>& AutoGeneratedNodes,
UMaterialFunctionMaterialLayer* TargetMaterialLayer,
UMaterialFunction* GetFeatureIdsFromAttributeFunction,
int32& NodeX,
int32& NodeY) {
FString SafeName = createHlslSafeName(Description.Name);
UMaterialExpressionScalarParameter* TextureCoordinateIndex =
NewObject<UMaterialExpressionScalarParameter>(TargetMaterialLayer);
TextureCoordinateIndex->ParameterName = FName(SafeName);
TextureCoordinateIndex->DefaultValue = 0.0f;
TextureCoordinateIndex->MaterialExpressionEditorX = NodeX;
TextureCoordinateIndex->MaterialExpressionEditorY = NodeY;
AutoGeneratedNodes.Add(TextureCoordinateIndex);
NodeX += Incr *
(0.2f + GetNameLengthScalar(TextureCoordinateIndex->ParameterName));
UMaterialExpressionMaterialFunctionCall* GetFeatureIdsFromAttribute =
NewObject<UMaterialExpressionMaterialFunctionCall>(TargetMaterialLayer);
GetFeatureIdsFromAttribute->MaterialFunction =
GetFeatureIdsFromAttributeFunction;
GetFeatureIdsFromAttribute->MaterialExpressionEditorX = NodeX;
GetFeatureIdsFromAttribute->MaterialExpressionEditorY = NodeY;
GetFeatureIdsFromAttributeFunction->GetInputsAndOutputs(
GetFeatureIdsFromAttribute->FunctionInputs,
GetFeatureIdsFromAttribute->FunctionOutputs);
GetFeatureIdsFromAttribute->FunctionInputs[0].Input.Expression =
TextureCoordinateIndex;
AutoGeneratedNodes.Add(GetFeatureIdsFromAttribute);
NodeX += 2 * Incr;
return GetFeatureIdsFromAttribute;
}
/**
* @brief Generates the nodes necessary to account for the null feature ID value
* from a feature ID set.
*/
void GenerateNodesForNullFeatureId(
const FCesiumFeatureIdSetDescription& Description,
TArray<UMaterialExpression*>& AutoGeneratedNodes,
UMaterialFunctionMaterialLayer* TargetMaterialLayer,
int32& NodeX,
int32& NodeY,
UMaterialExpression* LastNode) {
int32 SectionTop = NodeY;
NodeY += 0.5 * Incr;
FString SafeName = createHlslSafeName(Description.Name);
UMaterialExpressionScalarParameter* NullFeatureId =
NewObject<UMaterialExpressionScalarParameter>(TargetMaterialLayer);
NullFeatureId->ParameterName = FName(SafeName + MaterialNullFeatureIdSuffix);
NullFeatureId->DefaultValue = 0;
NullFeatureId->MaterialExpressionEditorX = NodeX;
NullFeatureId->MaterialExpressionEditorY = NodeY;
AutoGeneratedNodes.Add(NullFeatureId);
NodeY = SectionTop;
NodeX += Incr * (0.75 + GetNameLengthScalar(NullFeatureId->ParameterName));
UMaterialExpressionIf* IfStatement =
NewObject<UMaterialExpressionIf>(TargetMaterialLayer);
IfStatement->A.Expression = LastNode;
IfStatement->B.Expression = NullFeatureId;
IfStatement->MaterialExpressionEditorX = NodeX;
IfStatement->MaterialExpressionEditorY = NodeY;
AutoGeneratedNodes.Add(IfStatement);
}
/**
* @brief Generates a parameter node corresponding to the given encoded metadata
* type.
*/
UMaterialExpressionParameter* GenerateParameterNodeWithGivenType(
const ECesiumEncodedMetadataType Type,
const FString& Name,
TArray<UMaterialExpression*>& AutoGeneratedNodes,
UMaterialFunctionMaterialLayer* TargetMaterialLayer,
int32 NodeX,
int32 NodeY) {
UMaterialExpressionParameter* Parameter = nullptr;
int32 NodeHeight = 0;
if (Type == ECesiumEncodedMetadataType::Scalar) {
UMaterialExpressionScalarParameter* ScalarParameter =
NewObject<UMaterialExpressionScalarParameter>(TargetMaterialLayer);
ScalarParameter->DefaultValue = 0.0f;
Parameter = ScalarParameter;
}
if (Type == ECesiumEncodedMetadataType::Vec2 ||
Type == ECesiumEncodedMetadataType::Vec3 ||
Type == ECesiumEncodedMetadataType::Vec4) {
UMaterialExpressionVectorParameter* VectorParameter =
NewObject<UMaterialExpressionVectorParameter>(TargetMaterialLayer);
VectorParameter->DefaultValue = FLinearColor(0.0f, 0.0f, 0.0f, 0.0f);
Parameter = VectorParameter;
}
if (!Parameter) {
return nullptr;
}
Parameter->ParameterName = FName(Name);
Parameter->MaterialExpressionEditorX = NodeX;
Parameter->MaterialExpressionEditorY = NodeY;
AutoGeneratedNodes.Add(Parameter);
return Parameter;
}
/**
* @brief Generates the nodes necessary to apply property transforms to a
* metadata property.
*/
void GenerateNodesForMetadataPropertyTransforms(
const FCesiumMetadataPropertyDetails& PropertyDetails,
ECesiumEncodedMetadataType Type,
const FString& PropertyName,
const FString& FullPropertyName,
TArray<UMaterialExpression*>& AutoGeneratedNodes,
UMaterialFunctionMaterialLayer* TargetMaterialLayer,
int32& NodeX,
int32& NodeY,
UMaterialExpressionCustom* GetPropertyValuesFunction,
int32 GetPropertyValuesOutputIndex) {
int32 BeginSectionX = NodeX;
int32 BeginSectionY = NodeY;
UMaterialExpressionCustom* ApplyTransformsFunction = nullptr;
UMaterialExpression* GetNoDataValueNode = nullptr;
UMaterialExpression* GetDefaultValueNode = nullptr;
UMaterialExpressionIf* NoDataIfNode = nullptr;
// This section corresponds to the parameter nodes on the left that actually
// supply the transform values for a property.
int32 MaximumParameterSectionX = 0;
TArray<UMaterialExpression*> NodesToMove;
ECustomMaterialOutputType OutputType = GetOutputTypeForEncodedType(Type);
if (PropertyDetails.bIsNormalized || PropertyDetails.bHasScale ||
PropertyDetails.bHasOffset) {
ApplyTransformsFunction =
NewObject<UMaterialExpressionCustom>(TargetMaterialLayer);
ApplyTransformsFunction->Code = "";
ApplyTransformsFunction->Description =
ApplyValueTransformsPrefix + PropertyName;
ApplyTransformsFunction->MaterialExpressionEditorX =
BeginSectionX + 0.5 * Incr;
ApplyTransformsFunction->MaterialExpressionEditorY = NodeY;
ApplyTransformsFunction->Inputs.Reserve(3);
ApplyTransformsFunction->Outputs.Reset(2);
ApplyTransformsFunction->AdditionalOutputs.Reserve(1);
ApplyTransformsFunction->Outputs.Add(FExpressionOutput(FName("Raw Value")));
ApplyTransformsFunction->bShowOutputNameOnPin = true;
AutoGeneratedNodes.Add(ApplyTransformsFunction);
NodesToMove.Add(ApplyTransformsFunction);
FCustomInput& RawValueInput = ApplyTransformsFunction->Inputs[0];
RawValueInput.InputName = FName("RawValue");
RawValueInput.Input.Expression = GetPropertyValuesFunction;
RawValueInput.Input.OutputIndex = GetPropertyValuesOutputIndex;
FCustomOutput& TransformedOutput =
ApplyTransformsFunction->AdditionalOutputs.Emplace_GetRef();
TransformedOutput.OutputName = FName("TransformedValue");
ApplyTransformsFunction->Outputs.Add(
FExpressionOutput(TransformedOutput.OutputName));
TransformedOutput.OutputType = OutputType;
FString TransformCode = "TransformedValue = ";
if (PropertyDetails.bIsNormalized) {
// Normalization can be hardcoded because only normalized uint8s are
// supported.
TransformCode += "(RawValue / 255.0f)";
} else {
TransformCode += "RawValue";
}
if (PropertyDetails.bHasScale) {
NodeY += Incr;
UMaterialExpressionParameter* Parameter =
GenerateParameterNodeWithGivenType(
Type,
FullPropertyName + MaterialPropertyScaleSuffix,
AutoGeneratedNodes,
TargetMaterialLayer,
BeginSectionX,
NodeY);
FString ScaleName = "Scale";
FCustomInput& DefaultInput =
ApplyTransformsFunction->Inputs.Emplace_GetRef();
DefaultInput.InputName = FName(ScaleName);
DefaultInput.Input.Expression = Parameter;
TransformCode += " * " + ScaleName;
MaximumParameterSectionX = FMath::Max(
MaximumParameterSectionX,
Incr * GetNameLengthScalar(Parameter->ParameterName));
}
if (PropertyDetails.bHasOffset) {
NodeY += Incr;
UMaterialExpressionParameter* Parameter =
GenerateParameterNodeWithGivenType(
Type,
FullPropertyName + MaterialPropertyOffsetSuffix,
AutoGeneratedNodes,
TargetMaterialLayer,
BeginSectionX,
NodeY);
FString OffsetName = "Offset";
FCustomInput& DefaultInput =
ApplyTransformsFunction->Inputs.Emplace_GetRef();
DefaultInput.InputName = FName(OffsetName);
DefaultInput.Input.Expression = Parameter;
TransformCode += " + " + OffsetName;
MaximumParameterSectionX = FMath::Max(
MaximumParameterSectionX,
Incr * GetNameLengthScalar(Parameter->ParameterName));
}
// Example: TransformedValue = (RawValue / 255.0f) * Scale_VALUE +
// Offset_VALUE;
ApplyTransformsFunction->Code += TransformCode + ";\n";
// Return the raw value.
ApplyTransformsFunction->OutputType = OutputType;
ApplyTransformsFunction->Code += "return RawValue;";
NodeX +=
Incr * (1 + GetNameLengthScalar(ApplyTransformsFunction->Description));
}
FString swizzle = GetSwizzleForEncodedType(Type);
if (PropertyDetails.bHasNoDataValue) {
NodeY += Incr;
UMaterialExpressionParameter* Parameter =
GenerateParameterNodeWithGivenType(
Type,
FullPropertyName + MaterialPropertyNoDataSuffix,
AutoGeneratedNodes,
TargetMaterialLayer,
BeginSectionX,
NodeY);
int32 NameLength = Incr * GetNameLengthScalar(Parameter->ParameterName);
if (Type == ECesiumEncodedMetadataType::Scalar) {
// No additional work needs to be done to retrieve the scalar, so don't
// an extra unnecessary node.
GetNoDataValueNode = Parameter;
} else {
// This is equivalent to a "MakeFloatN" function.
UMaterialExpressionCustom* CustomFunction =
NewObject<UMaterialExpressionCustom>(TargetMaterialLayer);
CustomFunction->Description = "Get No Data Value For " + PropertyName;
CustomFunction->MaterialExpressionEditorX = BeginSectionX + 0.5 * Incr;
CustomFunction->MaterialExpressionEditorY = NodeY;
CustomFunction->Outputs.Reset(1);
CustomFunction->bShowOutputNameOnPin = true;
NodesToMove.Add(CustomFunction);
AutoGeneratedNodes.Add(CustomFunction);
FString NoDataName = "NoData";
FString InputName = NoDataName + MaterialPropertyValueSuffix;
FCustomInput& NoDataInput = CustomFunction->Inputs[0];
NoDataInput.InputName = FName(InputName);
NoDataInput.Input.Expression = Parameter;
CustomFunction->Outputs.Add(FExpressionOutput(FName(NoDataName)));
CustomFunction->OutputType = OutputType;
CustomFunction->Code = "return " + InputName + swizzle + ";\n";
GetNoDataValueNode = CustomFunction;
NameLength += GetNameLengthScalar(CustomFunction->Description) * Incr;
}
MaximumParameterSectionX =
FMath::Max(MaximumParameterSectionX, 0.25 * Incr);
}
if (PropertyDetails.bHasDefaultValue) {
NodeY += 0.75 * Incr;
UMaterialExpressionParameter* Parameter =
GenerateParameterNodeWithGivenType(
Type,
FullPropertyName + MaterialPropertyDefaultValueSuffix,
AutoGeneratedNodes,
TargetMaterialLayer,
BeginSectionX,
NodeY);
int32 NameLength = Incr * GetNameLengthScalar(Parameter->ParameterName);
if (Type == ECesiumEncodedMetadataType::Scalar) {
// No additional work needs to be done to retrieve the scalar, so don't
// an extra unnecessary node.
GetDefaultValueNode = Parameter;
} else {
// This is equivalent to a "MakeFloatN" function.
UMaterialExpressionCustom* CustomFunction =
NewObject<UMaterialExpressionCustom>(TargetMaterialLayer);
CustomFunction->Description = "Get Default Value For " + PropertyName;
CustomFunction->MaterialExpressionEditorX = BeginSectionX + 0.5 * Incr;
CustomFunction->MaterialExpressionEditorY = NodeY;
CustomFunction->Outputs.Reset(1);
CustomFunction->bShowOutputNameOnPin = true;
NodesToMove.Add(CustomFunction);
AutoGeneratedNodes.Add(CustomFunction);
FString DefaultName = "Default";
FString InputName = DefaultName + MaterialPropertyValueSuffix;
FCustomInput& DefaultInput = CustomFunction->Inputs[0];
DefaultInput.InputName = FName(InputName);
DefaultInput.Input.Expression = Parameter;
CustomFunction->Outputs.Add(FExpressionOutput(FName("Default Value")));
CustomFunction->OutputType = OutputType;
// Example: Default = Default_VALUE.xyz;
CustomFunction->Code = "return " + InputName + swizzle + ";\n";
GetDefaultValueNode = CustomFunction;
NameLength += GetNameLengthScalar(CustomFunction->Description) * Incr;
}
MaximumParameterSectionX = FMath::Max(
MaximumParameterSectionX,
Incr * GetNameLengthScalar(Parameter->ParameterName));
}
for (UMaterialExpression* Node : NodesToMove) {
Node->MaterialExpressionEditorX += MaximumParameterSectionX;
}
NodesToMove.Empty();
NodeX += FMath::Max(2 * Incr, MaximumParameterSectionX + Incr);
// We want to return to the top of the section and work down again, without
// overwriting NodeY. At the end, we use the maximum value to determine the
// vertical extent of the entire section.
int32 SectionNodeY = BeginSectionY;
// Add if statement for resolving the no data / default values
if (GetNoDataValueNode) {
NodeX += Incr;
NoDataIfNode = NewObject<UMaterialExpressionIf>(TargetMaterialLayer);
NoDataIfNode->MaterialExpressionEditorX = NodeX;
NoDataIfNode->MaterialExpressionEditorY = SectionNodeY;
NoDataIfNode->B.Expression = GetNoDataValueNode;
NoDataIfNode->AEqualsB.Expression = GetDefaultValueNode;
if (ApplyTransformsFunction) {
NoDataIfNode->A.Expression = ApplyTransformsFunction;
NoDataIfNode->A.OutputIndex = 0;
NoDataIfNode->AGreaterThanB.Expression = ApplyTransformsFunction;
NoDataIfNode->AGreaterThanB.OutputIndex = 1;
NoDataIfNode->ALessThanB.Expression = ApplyTransformsFunction;
NoDataIfNode->ALessThanB.OutputIndex = 1;
} else {
NoDataIfNode->A.Expression = GetPropertyValuesFunction;
NoDataIfNode->A.OutputIndex = GetPropertyValuesOutputIndex;
NoDataIfNode->AGreaterThanB.Expression = GetPropertyValuesFunction;
NoDataIfNode->AGreaterThanB.OutputIndex = GetPropertyValuesOutputIndex;
NoDataIfNode->ALessThanB.Expression = GetPropertyValuesFunction;
NoDataIfNode->ALessThanB.OutputIndex = GetPropertyValuesOutputIndex;
}
AutoGeneratedNodes.Add(NoDataIfNode);
NodeX += 2 * Incr;
}
// If the property has a default value defined, it may be omitted from an
// instance of a property table, texture, or attribute. In this case, the
// default value should be used without needing to execute the
// GetPropertyValues function. We check this with a scalar parameter that
// acts as a boolean.
if (GetDefaultValueNode) {
UMaterialExpressionScalarParameter* HasValueParameter =
NewObject<UMaterialExpressionScalarParameter>(TargetMaterialLayer);
HasValueParameter->DefaultValue = 0.0f;
HasValueParameter->ParameterName =
FName(FullPropertyName + MaterialPropertyHasValueSuffix);
HasValueParameter->MaterialExpressionEditorX = NodeX;
HasValueParameter->MaterialExpressionEditorY = SectionNodeY;
AutoGeneratedNodes.Add(HasValueParameter);
NodeX += Incr * (1 + GetNameLengthScalar(HasValueParameter->ParameterName));
UMaterialExpressionIf* IfStatement =
NewObject<UMaterialExpressionIf>(TargetMaterialLayer);
IfStatement->MaterialExpressionEditorX = NodeX;
IfStatement->MaterialExpressionEditorY = SectionNodeY;
IfStatement->A.Expression = HasValueParameter;
IfStatement->ConstB = 1.0f;
IfStatement->ALessThanB.Expression = GetDefaultValueNode;
if (NoDataIfNode) {
IfStatement->AGreaterThanB.Expression = NoDataIfNode;
IfStatement->AEqualsB.Expression = NoDataIfNode;
} else if (ApplyTransformsFunction) {
IfStatement->AGreaterThanB.Expression = ApplyTransformsFunction;
IfStatement->AGreaterThanB.OutputIndex = 1;
IfStatement->AEqualsB.Expression = ApplyTransformsFunction;
IfStatement->AEqualsB.OutputIndex = 1;
} else {
IfStatement->AGreaterThanB.Expression = GetPropertyValuesFunction;
IfStatement->AGreaterThanB.OutputIndex = GetPropertyValuesOutputIndex;
IfStatement->AEqualsB.Expression = GetPropertyValuesFunction;
IfStatement->AEqualsB.OutputIndex = GetPropertyValuesOutputIndex;
}
AutoGeneratedNodes.Add(IfStatement);
}
if (SectionNodeY > NodeY) {
NodeY = SectionNodeY;
}
NodeY += Incr;
}
/**
* @brief Generates the nodes necessary to retrieve values from a property
* table.
*/
void GenerateNodesForPropertyTable(
const FCesiumPropertyTableDescription& PropertyTable,
TArray<UMaterialExpression*>& AutoGeneratedNodes,
UMaterialFunctionMaterialLayer* TargetMaterialLayer,
int32& NodeX,
int32& NodeY,
UMaterialExpression* GetFeatureExpression) {
int32 BeginSectionX = NodeX;
// This value is used by parameters on the left side of the
// "GetPropertyValues" function...
int32 PropertyDataSectionY = NodeY - 0.5 * Incr;
// ...whereas this value is used for parameters on the right side of the
// function.
int32 PropertyTransformsSectionY = NodeY + 20;
UMaterialExpressionCustom* GetPropertyValuesFunction =
NewObject<UMaterialExpressionCustom>(TargetMaterialLayer);
GetPropertyValuesFunction->Inputs.Reserve(PropertyTable.Properties.Num() + 2);
GetPropertyValuesFunction->Outputs.Reset(PropertyTable.Properties.Num() + 1);
GetPropertyValuesFunction->Outputs.Add(FExpressionOutput(TEXT("Feature ID")));
GetPropertyValuesFunction->bShowOutputNameOnPin = true;
GetPropertyValuesFunction->Code = "";
GetPropertyValuesFunction->Description =
GetPropertyValuesPrefix + PropertyTable.Name;
GetPropertyValuesFunction->MaterialExpressionEditorX = NodeX;
GetPropertyValuesFunction->MaterialExpressionEditorY = NodeY;
AutoGeneratedNodes.Add(GetPropertyValuesFunction);
int32 GetPropertyValuesFunctionWidth =
Incr * GetNameLengthScalar(GetPropertyValuesFunction->Description);
// To prevent nodes from overlapping -- especially if they have really long
// names -- the GetPropertyValuesFunction node will be shifted to the right
// depending on the longest name among the parameters on the left.
int32 MaximumPropertyDataSectionX = 0;
// In a similar vein, this tracks the overall width of the property transforms
// section. This will be added to NodeX at the end so that nodes can continue
// to spawn horizontally.
int32 MaximumPropertyTransformsSectionX = 0;
// The nodes to the right of GetPropertyValuesFunction will also need to be
// shifted, hence this array to keep track of them.
TArray<UMaterialExpression*> PropertyTransformNodes;
FCustomInput& FeatureIDInput = GetPropertyValuesFunction->Inputs[0];
FeatureIDInput.InputName = FName("FeatureID");
FeatureIDInput.Input.Expression = GetFeatureExpression;
GetPropertyValuesFunction->AdditionalOutputs.Reserve(
PropertyTable.Properties.Num());
FString PropertyTableName = createHlslSafeName(PropertyTable.Name);
bool foundFirstProperty = false;
for (const FCesiumPropertyTablePropertyDescription& Property :
PropertyTable.Properties) {
if (Property.EncodingDetails.Conversion ==
ECesiumEncodedMetadataConversion::None ||
!Property.EncodingDetails.HasValidType()) {
continue;
}
PropertyDataSectionY += Incr;
FString PropertyName = createHlslSafeName(Property.Name);
// Example: "roofColor_DATA"
FString PropertyDataName = PropertyName + MaterialPropertyDataSuffix;
if (!foundFirstProperty) {
// Get the dimensions of the first valid property. All the properties
// will have the same pixel dimensions since it is based on the feature
// count.
GetPropertyValuesFunction->Code +=
"uint _czm_width;\nuint _czm_height;\n";
GetPropertyValuesFunction->Code +=
PropertyDataName + ".GetDimensions(_czm_width, _czm_height);\n";
GetPropertyValuesFunction->Code +=
"uint _czm_featureIndex = round(FeatureID);\n";
GetPropertyValuesFunction->Code +=
"uint _czm_pixelX = _czm_featureIndex % _czm_width;\n";
GetPropertyValuesFunction->Code +=
"uint _czm_pixelY = _czm_featureIndex / _czm_width;\n";
foundFirstProperty = true;
}
UMaterialExpressionTextureObjectParameter* PropertyData =
NewObject<UMaterialExpressionTextureObjectParameter>(
TargetMaterialLayer);
FString FullPropertyName = getMaterialNameForPropertyTableProperty(
PropertyTableName,
PropertyName);
PropertyData->ParameterName = FName(FullPropertyName);
PropertyData->MaterialExpressionEditorX = BeginSectionX;
PropertyData->MaterialExpressionEditorY = PropertyDataSectionY;
AutoGeneratedNodes.Add(PropertyData);
MaximumPropertyDataSectionX = FMath::Max(
MaximumPropertyDataSectionX,
Incr * GetNameLengthScalar(PropertyData->ParameterName));
FCustomInput& PropertyInput =
GetPropertyValuesFunction->Inputs.Emplace_GetRef();
PropertyInput.InputName = FName(PropertyDataName);
PropertyInput.Input.Expression = PropertyData;
FCustomOutput& PropertyOutput =
GetPropertyValuesFunction->AdditionalOutputs.Emplace_GetRef();
FString OutputName = PropertyName;
if (Property.PropertyDetails.bIsNormalized ||
Property.PropertyDetails.bHasOffset ||
Property.PropertyDetails.bHasScale) {
OutputName += MaterialPropertyRawSuffix;
}
PropertyOutput.OutputName = FName(OutputName);
GetPropertyValuesFunction->Outputs.Add(
FExpressionOutput(PropertyOutput.OutputName));
FString swizzle = GetSwizzleForEncodedType(Property.EncodingDetails.Type);
PropertyOutput.OutputType =
GetOutputTypeForEncodedType(Property.EncodingDetails.Type);
FString asComponentString =
Property.EncodingDetails.ComponentType ==
ECesiumEncodedMetadataComponentType::Float
? "asfloat"
: "asuint";
// Example:
// "color = asfloat(color_DATA.Load(int3(_czm_pixelX, _czm_pixelY,
// 0)).rgb);"
GetPropertyValuesFunction->Code +=
OutputName + " = " + asComponentString + "(" + PropertyDataName +
".Load(int3(_czm_pixelX, _czm_pixelY, 0))" + swizzle + ");\n";
if (Property.PropertyDetails.HasValueTransforms()) {
int32 PropertyTransformsSectionX =
0.25 * Incr + GetPropertyValuesFunctionWidth;
GenerateNodesForMetadataPropertyTransforms(
Property.PropertyDetails,
Property.EncodingDetails.Type,
PropertyName,
FullPropertyName,
PropertyTransformNodes,
TargetMaterialLayer,
PropertyTransformsSectionX,
PropertyTransformsSectionY,
GetPropertyValuesFunction,
GetPropertyValuesFunction->Outputs.Num() - 1);
MaximumPropertyTransformsSectionX = FMath::Max(
MaximumPropertyTransformsSectionX,
PropertyTransformsSectionX);
}
}
// Shift the X of GetPropertyValues depending on the width of the data
// parameters.
GetPropertyValuesFunction->MaterialExpressionEditorX +=
MaximumPropertyDataSectionX + Incr;
NodeX = GetPropertyValuesFunction->MaterialExpressionEditorX +
GetPropertyValuesFunctionWidth;
// Reposition all of the nodes related to property transforms.
for (UMaterialExpression* Node : PropertyTransformNodes) {
Node->MaterialExpressionEditorX +=
GetPropertyValuesFunction->MaterialExpressionEditorX;
AutoGeneratedNodes.Add(Node);
}
// Return the feature ID.
GetPropertyValuesFunction->OutputType =
ECustomMaterialOutputType::CMOT_Float1;
GetPropertyValuesFunction->Code += "return FeatureID;";
NodeX = GetPropertyValuesFunction->MaterialExpressionEditorX +
GetPropertyValuesFunctionWidth + MaximumPropertyTransformsSectionX +
Incr;
NodeY = FMath::Max(PropertyDataSectionY, PropertyTransformsSectionY) + Incr;
}
/**
* @brief Generates the nodes necessary to retrieve values from a property
* texture. In summary:
* - Gets UVs from primitive with SelectTexCoords (if applicable)
* - Creates GetPropertyValuesFrom function for property texture
* - Adds parameter nodes for each property's UV index, data, and channels array
* - Creates nodes to handle property transforms (if applicable)
*/
void GenerateNodesForPropertyTexture(
const FCesiumPropertyTextureDescription& PropertyTexture,
TArray<UMaterialExpression*>& AutoGeneratedNodes,
UMaterialFunctionMaterialLayer* TargetMaterialLayer,
const MaterialFunctionLibrary& FunctionLibrary,
int32& NodeX,
int32& NodeY,
bool bHasTexCoords) {
int32 BeginSectionX = NodeX;
// This value is used by parameters on the left side of the
// "GetPropertyValues" function...
int32 PropertyDataSectionY = NodeY;
// ...whereas this value is used for parameters on the right side of the
// function.
int32 PropertyTransformsSectionY = NodeY + 20;
UMaterialExpressionCustom* GetPropertyValuesFunction =
NewObject<UMaterialExpressionCustom>(TargetMaterialLayer);
GetPropertyValuesFunction->Inputs.Reset(3 * PropertyTexture.Properties.Num());
GetPropertyValuesFunction->Outputs.Reset(
PropertyTexture.Properties.Num() + 1);
GetPropertyValuesFunction->Outputs.Add(FExpressionOutput(TEXT("return")));
GetPropertyValuesFunction->bShowOutputNameOnPin = true;
GetPropertyValuesFunction->Code = "";
GetPropertyValuesFunction->Description =
GetPropertyValuesPrefix + PropertyTexture.Name;
GetPropertyValuesFunction->MaterialExpressionEditorX = NodeX;
GetPropertyValuesFunction->MaterialExpressionEditorY = NodeY;
AutoGeneratedNodes.Add(GetPropertyValuesFunction);
int32 GetPropertyValuesFunctionWidth =
Incr * GetNameLengthScalar(GetPropertyValuesFunction->Description);
// To prevent nodes from overlapping -- especially if they have really long
// names -- the GetPropertyValuesFunction node will be shifted to the right
// depending on the longest name among the parameters on the left.
int32 MaximumPropertyDataSectionX = 0;
// In a similar vein, this tracks the overall width of the property transforms
// section. This will be added to NodeX at the end so that nodes can continue
// to spawn horizontally.
int32 MaximumPropertyTransformsSectionX = 0;
// The nodes to the right of GetPropertyValuesFunction will also need to be
// shifted, hence this array to keep track of them.
TArray<UMaterialExpression*> PropertyTransformNodes;
FString PropertyTextureName = createHlslSafeName(PropertyTexture.Name);
bool foundFirstProperty = false;
for (const FCesiumPropertyTexturePropertyDescription& Property :
PropertyTexture.Properties) {
if (!isSupportedPropertyTextureProperty(Property.PropertyDetails)) {
// Ignore properties that are unsupported, i.e., properties that require
// more than four bytes to parse values from. This limitation is imposed
// by cesium-native because only single-byte channels are supported.
UE_LOG(
LogCesium,
Warning,
TEXT(
"Skipping material node generation for unsupported property texture property %s in %s."),
*Property.Name,
*PropertyTexture.Name);
continue;
}
FString PropertyName = createHlslSafeName(Property.Name);
FString FullPropertyName = getMaterialNameForPropertyTextureProperty(
PropertyTextureName,
PropertyName);
ECesiumEncodedMetadataType Type =
CesiumMetadataPropertyDetailsToEncodingDetails(Property.PropertyDetails)
.Type;
if (!foundFirstProperty) {
// Define this helper function at the beginning of the code. This extracts
// the correct value from a float4 based on the given channel index.
// This is needed because the code input[index] doesn't seem to work with
// a dynamic index.
FString StructName =
MaterialPropertyTexturePrefix + PropertyTextureName + "Functions";
// clang-format off
GetPropertyValuesFunction->Code +=
"struct " + StructName + " {\n "
" float Get(float4 input, uint index) {\n"
" switch (index) {\n"
" case 0:\n return input.r;\n"
" case 1:\n return input.g;\n"
" case 2:\n return input.b;\n"
" case 3:\n return input.a;\n"
" default:\n return 0.0f;\n"
" }\n"
" }\n"
"};\n" +
StructName + " f;\n";
// clang-format on
// Also declare some temporary variables for later use.
GetPropertyValuesFunction->Code +=
"float4 sampleColor = float4(0, 0, 0, 0);\n"
"uint byteOffset = 0;\n"
"uint sample = 0;\n"
"uint channel = 0;\n\n";
foundFirstProperty = true;
}
UMaterialExpressionMaterialFunctionCall* TexCoordsInputFunction = nullptr;
if (bHasTexCoords) {
UMaterialExpressionScalarParameter* TexCoordsIndex =
NewObject<UMaterialExpressionScalarParameter>(TargetMaterialLayer);
TexCoordsIndex->ParameterName =
FName(FullPropertyName + MaterialTexCoordIndexSuffix);
TexCoordsIndex->DefaultValue = 0.0f;
TexCoordsIndex->MaterialExpressionEditorY = NodeX;
TexCoordsIndex->MaterialExpressionEditorY = PropertyDataSectionY;
AutoGeneratedNodes.Add(TexCoordsIndex);
NodeX +=
Incr * (GetNameLengthScalar(TexCoordsIndex->ParameterName) + 0.2f);
UMaterialExpressionMaterialFunctionCall* SelectTexCoords =
NewObject<UMaterialExpressionMaterialFunctionCall>(
TargetMaterialLayer);
SelectTexCoords->MaterialFunction = FunctionLibrary.SelectTexCoords;
SelectTexCoords->MaterialExpressionEditorX = NodeX;
SelectTexCoords->MaterialExpressionEditorY = PropertyDataSectionY;
FunctionLibrary.SelectTexCoords->GetInputsAndOutputs(
SelectTexCoords->FunctionInputs,
SelectTexCoords->FunctionOutputs);
SelectTexCoords->FunctionInputs[0].Input.Expression = TexCoordsIndex;
AutoGeneratedNodes.Add(SelectTexCoords);
TexCoordsInputFunction = SelectTexCoords;
MaximumPropertyDataSectionX = NodeX + 2 * Incr;
NodeX = BeginSectionX;
if (Property.bHasKhrTextureTransform) {
PropertyDataSectionY += 1.25 * Incr;
UMaterialExpressionVectorParameter* TransformRotation =
NewObject<UMaterialExpressionVectorParameter>(TargetMaterialLayer);
TransformRotation->ParameterName =
FName(FullPropertyName + MaterialTextureRotationSuffix);
TransformRotation->DefaultValue = {0, 1, 0, 1};
TransformRotation->MaterialExpressionEditorX = NodeX;
TransformRotation->MaterialExpressionEditorY = PropertyDataSectionY;
AutoGeneratedNodes.Add(TransformRotation);
UMaterialExpressionVectorParameter* TransformScaleOffset =
NewObject<UMaterialExpressionVectorParameter>(TargetMaterialLayer);
TransformScaleOffset->ParameterName =
FName(FullPropertyName + MaterialTextureScaleOffsetSuffix);
TransformScaleOffset->DefaultValue = {1, 1, 0, 0};
TransformScaleOffset->MaterialExpressionEditorX = NodeX;
TransformScaleOffset->MaterialExpressionEditorY =
PropertyDataSectionY + Incr;
AutoGeneratedNodes.Add(TransformScaleOffset);
UMaterialExpressionAppendVector* AppendScale =
NewObject<UMaterialExpressionAppendVector>(TargetMaterialLayer);
AppendScale->MaterialExpressionEditorX =
NodeX + Incr * (0.5 + GetNameLengthScalar(
TransformScaleOffset->ParameterName));
AppendScale->MaterialExpressionEditorY =
TransformRotation->MaterialExpressionEditorY;
AppendScale->A.Connect(1, TransformScaleOffset);
AppendScale->B.Connect(2, TransformScaleOffset);
AutoGeneratedNodes.Add(AppendScale);
UMaterialExpressionAppendVector* AppendOffset =
NewObject<UMaterialExpressionAppendVector>(TargetMaterialLayer);
AppendOffset->MaterialExpressionEditorX =
AppendScale->MaterialExpressionEditorX;
AppendOffset->MaterialExpressionEditorY =
TransformScaleOffset->MaterialExpressionEditorY;
AppendOffset->A.Connect(3, TransformScaleOffset);
AppendOffset->B.Connect(4, TransformScaleOffset);
AutoGeneratedNodes.Add(AppendOffset);
MaximumPropertyDataSectionX = FMath::Max(
MaximumPropertyDataSectionX,
AppendOffset->MaterialExpressionEditorX + Incr - NodeX);
PropertyDataSectionY += 1.25 * Incr;
UMaterialExpressionMaterialFunctionCall* TransformTexCoords =
NewObject<UMaterialExpressionMaterialFunctionCall>(
TargetMaterialLayer);
TransformTexCoords->MaterialFunction =
FunctionLibrary.TransformTexCoords;
TransformTexCoords->MaterialExpressionEditorX =
SelectTexCoords->MaterialExpressionEditorX + Incr * 1.5;
TransformTexCoords->MaterialExpressionEditorY =
SelectTexCoords->MaterialExpressionEditorY;
FunctionLibrary.TransformTexCoords->GetInputsAndOutputs(
TransformTexCoords->FunctionInputs,
TransformTexCoords->FunctionOutputs);
// For some reason, Connect() doesn't work with this input...
TransformTexCoords->FunctionInputs[0].Input.Expression =
SelectTexCoords;
TransformTexCoords->FunctionInputs[0].Input.OutputIndex = 0;
TransformTexCoords->FunctionInputs[1].Input.Connect(
0,
TransformRotation);
TransformTexCoords->FunctionInputs[2].Input.Connect(0, AppendScale);
TransformTexCoords->FunctionInputs[3].Input.Connect(0, AppendOffset);
AutoGeneratedNodes.Add(TransformTexCoords);
TexCoordsInputFunction = TransformTexCoords;
MaximumPropertyDataSectionX = FMath::Max(
MaximumPropertyDataSectionX,
TransformTexCoords->MaterialExpressionEditorX + Incr * 1.5);
}
PropertyDataSectionY += 0.8 * Incr;
}
UMaterialExpressionTextureObjectParameter* PropertyData =
NewObject<UMaterialExpressionTextureObjectParameter>(
TargetMaterialLayer);
PropertyData->ParameterName = FName(FullPropertyName);
PropertyData->MaterialExpressionEditorX = NodeX;
PropertyData->MaterialExpressionEditorY = PropertyDataSectionY;
AutoGeneratedNodes.Add(PropertyData);
MaximumPropertyDataSectionX = FMath::Max(
MaximumPropertyDataSectionX,
Incr * GetNameLengthScalar(PropertyData->ParameterName));
PropertyDataSectionY += Incr;
UMaterialExpressionVectorParameter* Channels =
NewObject<UMaterialExpressionVectorParameter>(TargetMaterialLayer);
Channels->ParameterName = FName(FullPropertyName + MaterialChannelsSuffix);
Channels->DefaultValue = FLinearColor(0, 0, 0, 0);
Channels->MaterialExpressionEditorX = NodeX;
Channels->MaterialExpressionEditorY = PropertyDataSectionY;
AutoGeneratedNodes.Add(Channels);
UMaterialExpressionAppendVector* AppendChannels =
NewObject<UMaterialExpressionAppendVector>(TargetMaterialLayer);
AppendChannels->MaterialExpressionEditorX =
NodeX + Incr * (1 + GetNameLengthScalar(Channels->ParameterName));
AppendChannels->MaterialExpressionEditorY = PropertyDataSectionY;
AppendChannels->A.Connect(0, Channels);
AppendChannels->B.Connect(4, Channels);
AutoGeneratedNodes.Add(AppendChannels);
MaximumPropertyDataSectionX = FMath::Max(
MaximumPropertyDataSectionX,
Incr * GetNameLengthScalar(Channels->ParameterName));
FCustomInput& TexCoordsInput =
GetPropertyValuesFunction->Inputs.Emplace_GetRef();
FString PropertyTextureUVName = PropertyName + MaterialPropertyUVSuffix;
TexCoordsInput.InputName = FName(PropertyTextureUVName);
TexCoordsInput.Input.Expression = TexCoordsInputFunction;
FCustomInput& PropertyTextureInput =
GetPropertyValuesFunction->Inputs.Emplace_GetRef();
FString PropertyTextureDataName = PropertyName + MaterialPropertyDataSuffix;
PropertyTextureInput.InputName = FName(PropertyTextureDataName);
PropertyTextureInput.Input.Expression = PropertyData;
FCustomInput& ChannelsInput =
GetPropertyValuesFunction->Inputs.Emplace_GetRef();
FString PropertyTextureChannelsName = PropertyName + MaterialChannelsSuffix;
ChannelsInput.InputName = FName(PropertyTextureChannelsName);
ChannelsInput.Input.Expression = AppendChannels;
FCustomOutput& PropertyOutput =
GetPropertyValuesFunction->AdditionalOutputs.Emplace_GetRef();
FString OutputName = PropertyName;
if (Property.PropertyDetails.bIsNormalized ||
Property.PropertyDetails.bHasOffset ||
Property.PropertyDetails.bHasScale) {
OutputName += MaterialPropertyRawSuffix;
}
PropertyOutput.OutputName = FName(OutputName);
GetPropertyValuesFunction->Outputs.Add(
FExpressionOutput(PropertyOutput.OutputName));
PropertyOutput.OutputType = GetOutputTypeForEncodedType(Type);
GetPropertyValuesFunction->Code += GenerateCodeForPropertyTextureProperty(
PropertyName,
PropertyTextureUVName,
PropertyTextureDataName,
PropertyTextureChannelsName,
Property.PropertyDetails);
if (Property.PropertyDetails.HasValueTransforms()) {
int32 PropertyTransformsSectionX =
0.2f * Incr + GetPropertyValuesFunctionWidth;
GenerateNodesForMetadataPropertyTransforms(
Property.PropertyDetails,
Type,
PropertyName,
FullPropertyName,
PropertyTransformNodes,
TargetMaterialLayer,
PropertyTransformsSectionX,
PropertyTransformsSectionY,
GetPropertyValuesFunction,
GetPropertyValuesFunction->Outputs.Num() - 1);
MaximumPropertyTransformsSectionX = FMath::Max(
MaximumPropertyTransformsSectionX,
PropertyTransformsSectionX);
}
PropertyDataSectionY += Incr;
}
// Set the X of GetPropertyValues.
GetPropertyValuesFunction->MaterialExpressionEditorX +=
MaximumPropertyDataSectionX + Incr;
// Reposition all of the nodes related to property transforms.
for (UMaterialExpression* Node : PropertyTransformNodes) {
Node->MaterialExpressionEditorX +=
GetPropertyValuesFunction->MaterialExpressionEditorX;
AutoGeneratedNodes.Add(Node);
}
// Obligatory return code.
GetPropertyValuesFunction->OutputType =
ECustomMaterialOutputType::CMOT_Float1;
GetPropertyValuesFunction->Code += "return 0.0f;";
NodeX = GetPropertyValuesFunction->MaterialExpressionEditorX +
GetPropertyValuesFunctionWidth + MaximumPropertyTransformsSectionX +
Incr;
NodeY = FMath::Max(PropertyDataSectionY, PropertyTransformsSectionY) + Incr;
}
UMaterialExpression* GenerateInstanceNodes(
TArray<UMaterialExpression*>& AutoGeneratedNodes,
UMaterialFunctionMaterialLayer* TargetMaterialLayer,
UMaterialFunction* GetFeatureIdsFromInstanceFunction,
int32& NodeX,
int32& NodeY) {
UMaterialExpressionMaterialFunctionCall* GetFeatureIds =
NewObject<UMaterialExpressionMaterialFunctionCall>(TargetMaterialLayer);
GetFeatureIds->MaterialFunction = GetFeatureIdsFromInstanceFunction;
GetFeatureIds->MaterialExpressionEditorX = NodeX;
GetFeatureIds->MaterialExpressionEditorY = NodeY;
GetFeatureIdsFromInstanceFunction->GetInputsAndOutputs(
GetFeatureIds->FunctionInputs,
GetFeatureIds->FunctionOutputs);
NodeX += 2 * Incr;
AutoGeneratedNodes.Add(GetFeatureIds);
return GetFeatureIds;
}
void GenerateMaterialNodes(
UCesiumFeaturesMetadataComponent* pComponent,
TArray<UMaterialExpression*>& AutoGeneratedNodes,
TArray<UMaterialExpression*>& OneTimeGeneratedNodes,
const MaterialFunctionLibrary& FunctionLibrary) {
int32 NodeX = 0;
int32 NodeY = 0;
int32 BeginSectionX = NodeX;
int32 MaximumSectionX = BeginSectionX;
TSet<FString> GeneratedPropertyTableNames;
GeneratedPropertyTableNames.Reserve(pComponent->PropertyTables.Num());
for (const FCesiumFeatureIdSetDescription& featureIdSet :
pComponent->FeatureIdSets) {
if (featureIdSet.Type == ECesiumFeatureIdSetType::None) {
continue;
}
UMaterialExpressionMaterialFunctionCall* GetFeatureIdCall = nullptr;
UMaterialExpression* LastNode = nullptr;
if (featureIdSet.Type == ECesiumFeatureIdSetType::Texture) {
LastNode = GenerateNodesForFeatureIdTexture(
featureIdSet,
AutoGeneratedNodes,
pComponent->TargetMaterialLayer,
FunctionLibrary,
NodeX,
NodeY);
} else if (featureIdSet.Type == ECesiumFeatureIdSetType::Instance) {
LastNode = GenerateInstanceNodes(
AutoGeneratedNodes,
pComponent->TargetMaterialLayer,
FunctionLibrary.GetFeatureIdsFromInstance,
NodeX,
NodeY);
} else {
// Handle implicit feature IDs the same as feature ID attributes
LastNode = GenerateNodesForFeatureIdAttribute(
featureIdSet,
AutoGeneratedNodes,
pComponent->TargetMaterialLayer,
FunctionLibrary.GetFeatureIdsFromAttribute,
NodeX,
NodeY);
}
int32 BeginSectionY = NodeY;
if (!featureIdSet.PropertyTableName.IsEmpty()) {
const FCesiumPropertyTableDescription* pPropertyTable =
pComponent->PropertyTables.FindByPredicate(
[&name = featureIdSet.PropertyTableName](
const FCesiumPropertyTableDescription&
existingPropertyTable) {
return existingPropertyTable.Name == name;
});
if (pPropertyTable) {
GenerateNodesForPropertyTable(
*pPropertyTable,
AutoGeneratedNodes,
pComponent->TargetMaterialLayer,
NodeX,
NodeY,
LastNode);
GeneratedPropertyTableNames.Add(pPropertyTable->Name);
}
}
if (featureIdSet.bHasNullFeatureId) {
// Spatial nitpicking; this aligns the if statement to the same Y as the
// PropertyTableFunction node then resets the Y so that the next section
// appears below all of the just-generated nodes.
int32 OriginalY = NodeY;
NodeY = BeginSectionY;
GenerateNodesForNullFeatureId(
featureIdSet,
AutoGeneratedNodes,
pComponent->TargetMaterialLayer,
NodeX,
NodeY,
LastNode);
NodeY = OriginalY;
}
MaximumSectionX = FMath::Max(MaximumSectionX, NodeX);
NodeX = BeginSectionX;
NodeY += 1.75 * Incr;
}
// Generate nodes for any property tables that aren't linked to a feature ID
// set.
for (const FCesiumPropertyTableDescription& propertyTable :
pComponent->PropertyTables) {
if (!GeneratedPropertyTableNames.Find(propertyTable.Name)) {
GenerateNodesForPropertyTable(
propertyTable,
AutoGeneratedNodes,
pComponent->TargetMaterialLayer,
NodeX,
NodeY,
nullptr);
MaximumSectionX = FMath::Max(MaximumSectionX, NodeX);
NodeX = BeginSectionX;
NodeY += 1.75 * Incr;
}
}
NodeY += Incr;
NodeX = BeginSectionX;
TSet<FString> GeneratedPropertyTextureNames;
GeneratedPropertyTextureNames.Reserve(pComponent->PropertyTextures.Num());
for (const FString& propertyTextureName : pComponent->PropertyTextureNames) {
const FCesiumPropertyTextureDescription* pPropertyTexture =
pComponent->PropertyTextures.FindByPredicate(
[&propertyTextureName](const FCesiumPropertyTextureDescription&
existingPropertyTexture) {
return existingPropertyTexture.Name == propertyTextureName;
});
if (!pPropertyTexture) {
continue;
}
GenerateNodesForPropertyTexture(
*pPropertyTexture,
AutoGeneratedNodes,
pComponent->TargetMaterialLayer,
FunctionLibrary,
NodeX,
NodeY,
true);
GeneratedPropertyTextureNames.Add(propertyTextureName);
MaximumSectionX = FMath::Max(MaximumSectionX, NodeX);
NodeY += 1.75 * Incr;
NodeX = BeginSectionX;
}
// Generate nodes for any property textures that aren't linked to a
// primitive / texture coordinate set.
for (const FCesiumPropertyTextureDescription& propertyTexture :
pComponent->PropertyTextures) {
if (!GeneratedPropertyTextureNames.Find(propertyTexture.Name)) {
GenerateNodesForPropertyTexture(
propertyTexture,
AutoGeneratedNodes,
pComponent->TargetMaterialLayer,
FunctionLibrary,
NodeX,
NodeY,
false);
MaximumSectionX = FMath::Max(MaximumSectionX, NodeX);
NodeY += 2 * Incr;
NodeX = BeginSectionX;
}
}
NodeY = -2 * Incr;
UMaterialExpressionFunctionInput* InputMaterial = nullptr;
for (const TObjectPtr<UMaterialExpression>& ExistingNode :
pComponent->TargetMaterialLayer->GetExpressionCollection().Expressions) {
UMaterialExpressionFunctionInput* ExistingInputMaterial =
Cast<UMaterialExpressionFunctionInput>(ExistingNode);
if (ExistingInputMaterial) {
InputMaterial = ExistingInputMaterial;
break;
}
}
if (!InputMaterial) {
InputMaterial = NewObject<UMaterialExpressionFunctionInput>(
pComponent->TargetMaterialLayer);
InputMaterial->InputType =
EFunctionInputType::FunctionInput_MaterialAttributes;
InputMaterial->bUsePreviewValueAsDefault = true;
InputMaterial->MaterialExpressionEditorX = NodeX;
InputMaterial->MaterialExpressionEditorY = NodeY;
OneTimeGeneratedNodes.Add(InputMaterial);
}
NodeX += BeginSectionX + MaximumSectionX;
UMaterialExpressionSetMaterialAttributes* SetMaterialAttributes = nullptr;
for (const TObjectPtr<UMaterialExpression>& ExistingNode :
pComponent->TargetMaterialLayer->GetExpressionCollection().Expressions) {
UMaterialExpressionSetMaterialAttributes* ExistingSetAttributes =
Cast<UMaterialExpressionSetMaterialAttributes>(ExistingNode);
if (ExistingSetAttributes) {
SetMaterialAttributes = ExistingSetAttributes;
break;
}
}
if (!SetMaterialAttributes) {
SetMaterialAttributes = NewObject<UMaterialExpressionSetMaterialAttributes>(
pComponent->TargetMaterialLayer);
OneTimeGeneratedNodes.Add(SetMaterialAttributes);
}
SetMaterialAttributes->Inputs[0].Expression = InputMaterial;
SetMaterialAttributes->MaterialExpressionEditorX = NodeX;
SetMaterialAttributes->MaterialExpressionEditorY = NodeY;
NodeX += 2 * Incr;
UMaterialExpressionFunctionOutput* OutputMaterial = nullptr;
for (const TObjectPtr<UMaterialExpression>& ExistingNode :
pComponent->TargetMaterialLayer->GetExpressionCollection().Expressions) {
UMaterialExpressionFunctionOutput* ExistingOutputMaterial =
Cast<UMaterialExpressionFunctionOutput>(ExistingNode);
if (ExistingOutputMaterial) {
OutputMaterial = ExistingOutputMaterial;
break;
}
}
if (!OutputMaterial) {
OutputMaterial = NewObject<UMaterialExpressionFunctionOutput>(
pComponent->TargetMaterialLayer);
OneTimeGeneratedNodes.Add(OutputMaterial);
}
OutputMaterial->MaterialExpressionEditorX = NodeX;
OutputMaterial->MaterialExpressionEditorY = NodeY;
OutputMaterial->A = FMaterialAttributesInput();
OutputMaterial->A.Expression = SetMaterialAttributes;
}
} // namespace
void UCesiumFeaturesMetadataComponent::GenerateMaterial() {
ACesium3DTileset* pTileset = Cast<ACesium3DTileset>(this->GetOwner());
if (!pTileset) {
return;
}
FString MaterialName =
"ML_" + pTileset->GetFName().ToString() + "_FeaturesMetadata";
FString PackageBaseName = "/Game/";
FString PackageName = PackageBaseName + MaterialName;
MaterialFunctionLibrary FunctionLibrary = MaterialFunctionLibrary();
if (!FunctionLibrary.isValid()) {
UE_LOG(
LogCesium,
Error,
TEXT(
"Can't find the material functions necessary to generate material. Aborting."));
return;
}
if (this->TargetMaterialLayer &&
this->TargetMaterialLayer->GetPackage()->IsDirty()) {
UE_LOG(
LogCesium,
Error,
TEXT(
"Can't regenerate a material layer that has unsaved changes. Please save your changes and try again."));
return;
}
bool Overwriting = false;
if (this->TargetMaterialLayer) {
// Overwriting an existing material layer.
Overwriting = true;
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()
->CloseAllEditorsForAsset(this->TargetMaterialLayer);
} else {
UPackage* Package = CreatePackage(*PackageName);
// Create an Unreal material layer
UMaterialFunctionMaterialLayerFactory* MaterialFactory =
NewObject<UMaterialFunctionMaterialLayerFactory>();
this->TargetMaterialLayer =
(UMaterialFunctionMaterialLayer*)MaterialFactory->FactoryCreateNew(
UMaterialFunctionMaterialLayer::StaticClass(),
Package,
*MaterialName,
RF_Public | RF_Standalone | RF_Transactional,
NULL,
GWarn);
FAssetRegistryModule::AssetCreated(this->TargetMaterialLayer);
Package->FullyLoad();
Package->SetDirtyFlag(true);
}
this->TargetMaterialLayer->PreEditChange(NULL);
// Maps autogenerated nodes to the FExpressionInputs that it previously sent
// its outputs to.
TMap<FString, TArray<FExpressionInput*>> ConnectionOutputRemap;
// Maps autogenerated nodes to the FExpressionInputs that it previously took
// inputs from.
TMap<FString, TMap<FString, const FExpressionInput*>> ConnectionInputRemap;
ClearAutoGeneratedNodes(
this->TargetMaterialLayer,
ConnectionInputRemap,
ConnectionOutputRemap,
FunctionLibrary);
TArray<UMaterialExpression*> AutoGeneratedNodes;
TArray<UMaterialExpression*> OneTimeGeneratedNodes;
GenerateMaterialNodes(
this,
AutoGeneratedNodes,
OneTimeGeneratedNodes,
FunctionLibrary);
// Add the generated nodes to the material.
for (UMaterialExpression* AutoGeneratedNode : AutoGeneratedNodes) {
// Mark as auto-generated. If the material is regenerated, we will look
// for this exact description to determine whether it was autogenerated.
AutoGeneratedNode->Desc = AutogeneratedMessage;
this->TargetMaterialLayer->GetExpressionCollection().AddExpression(
AutoGeneratedNode);
}
for (UMaterialExpression* OneTimeGeneratedNode : OneTimeGeneratedNodes) {
this->TargetMaterialLayer->GetExpressionCollection().AddExpression(
OneTimeGeneratedNode);
}
RemapUserConnections(
this->TargetMaterialLayer,
ConnectionInputRemap,
ConnectionOutputRemap,
FunctionLibrary);
// Let the material update itself if necessary
this->TargetMaterialLayer->PostEditChange();
// Make sure that any static meshes, etc using this material will stop
// using the FMaterialResource of the original material, and will use the
// new FMaterialResource created when we make a new UMaterial in place
FGlobalComponentReregisterContext RecreateComponents;
// If this is a new material, open the content browser to the auto-generated
// material.
if (!Overwriting) {
FContentBrowserModule* pContentBrowserModule =
FModuleManager::Get().GetModulePtr<FContentBrowserModule>(
"ContentBrowser");
if (pContentBrowserModule) {
TArray<UObject*> AssetsToHighlight;
AssetsToHighlight.Add(this->TargetMaterialLayer);
pContentBrowserModule->Get().SyncBrowserToAssets(AssetsToHighlight);
}
}
// Open the updated material in editor.
if (GEditor) {
UAssetEditorSubsystem* pAssetEditor =
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
if (pAssetEditor) {
GEngine->EndTransaction();
pAssetEditor->OpenEditorForAsset(this->TargetMaterialLayer);
IMaterialEditor* pMaterialEditor = static_cast<IMaterialEditor*>(
pAssetEditor->FindEditorForAsset(this->TargetMaterialLayer, true));
if (pMaterialEditor) {
pMaterialEditor->UpdateMaterialAfterGraphChange();
}
}
}
}
#endif // WITH_EDITOR