2756 lines
102 KiB
C++
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
|