More work on material instances

This commit is contained in:
2026-04-07 05:06:15 -04:00
parent e7fd6c2f70
commit b8cff00848
7 changed files with 445 additions and 371 deletions

View File

@@ -5,7 +5,7 @@
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingMaterialParameter.h"
#include "WingParameterEditor.h"
#include "Materials/MaterialInstanceConstant.h"
#include "MaterialTypes.h"
#include "MaterialInstance_ClearParameter.generated.h"
@@ -40,11 +40,11 @@ public:
if (!MI) return;
// Parse the parameter ID.
WingMaterialParameter::Info ID;
if (!WingMaterialParameter::ParseID(Parameter, ID, WingOut::Stdout))
FWingParameterEditor::Info ID;
if (!FWingParameterEditor::ParseID(Parameter, ID, WingOut::Stdout))
return;
if (!WingMaterialParameter::RemoveOverride(ID, MI, WingOut::Stdout)) return
if (!FWingParameterEditor::RemoveOverride(ID, MI, WingOut::Stdout)) return;
WingOut::Stdout.Printf(TEXT("Removed override\n"));
}
};

View File

@@ -5,7 +5,7 @@
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingMaterialParameter.h"
#include "WingParameterEditor.h"
#include "Materials/MaterialInstanceConstant.h"
#include "MaterialTypes.h"
#include "MaterialInstance_DumpParameters.generated.h"
@@ -35,8 +35,8 @@ public:
UMaterialInstanceConstant* MI = F.Asset(MaterialInstance).Cast<UMaterialInstanceConstant>();
if (!MI) return;
auto AllParams = WingMaterialParameter::GetMaterialParameters(MI);
WingMaterialParameter::PrintAll(AllParams, true);
auto AllParams = FWingParameterEditor::GetMaterialParameters(MI);
FWingParameterEditor::PrintAll(AllParams, true);
if (AllParams.IsEmpty()) WingOut::Stdout.Printf(TEXT("No material parameters.\n"));
}
};

View File

@@ -5,7 +5,7 @@
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingMaterialParameter.h"
#include "WingParameterEditor.h"
#include "Materials/MaterialInstanceConstant.h"
#include "MaterialTypes.h"
#include "MaterialInstance_SetParameter.generated.h"
@@ -42,19 +42,12 @@ public:
if (!MI) return;
// Parse the parameter ID.
WingMaterialParameter::Info ID;
if (!WingMaterialParameter::ParseID(Parameter, ID, WingOut::Stdout))
FWingParameterEditor::Info ID;
if (!FWingParameterEditor::ParseID(Parameter, ID, WingOut::Stdout))
return;
// Make sure the parameter actually exists.
auto AllParams = WingMaterialParameter::GetMaterialParameters(MI);
FMaterialParameterMetadata* Meta =
WingMaterialParameter::FindParameter(AllParams, ID, WingOut::Stdout);
if (!Meta) return;
if (!WingMaterialParameter::CheckNoCustomPrimitiveData(ID, *Meta, WingOut::Stdout)) return;
// Set the parameter
if (!WingMaterialParameter::AddOverride(ID, MI, Value, WingOut::Stdout)) return;
if (!FWingParameterEditor::AddOverride(ID, MI, Value, WingOut::Stdout)) return;
WingOut::Stdout.Printf(TEXT("Assigned.\n"));
}
};

View File

@@ -5,7 +5,7 @@
#include "WingFetcher.h"
#include "WingServer.h"
#include "WingUtils.h"
#include "WingMaterialParameter.h"
#include "WingParameterEditor.h"
#include "MaterialTypes.h"
#include "Material_DumpParameters.generated.h"
@@ -34,8 +34,8 @@ public:
UMaterial* Mat = F.Asset(Material).Cast<UMaterial>();
if (!Mat) return;
auto AllParams = WingMaterialParameter::GetMaterialParameters(Mat);
WingMaterialParameter::PrintAll(AllParams, false);
auto AllParams = FWingParameterEditor::GetMaterialParameters(Mat);
FWingParameterEditor::PrintAll(AllParams, false);
if (AllParams.IsEmpty()) WingOut::Stdout.Printf(TEXT("No material parameters.\n"));
}
};

View File

@@ -1,335 +0,0 @@
#include "WingMaterialParameter.h"
#include "WingUtils.h"
#include "WingTokenizer.h"
#include "WingServer.h"
#include "Engine/Font.h"
#include "Engine/TextureCollection.h"
#include "Misc/DefaultValueHelper.h"
#include "SparseVolumeTexture/SparseVolumeTexture.h"
#include "VT/RuntimeVirtualTexture.h"
const TCHAR *WingMaterialParameter::EMaterialParameterTypeToString(EMaterialParameterType Type)
{
switch (Type)
{
case EMaterialParameterType::Scalar: return TEXT("Scalar");
case EMaterialParameterType::Vector: return TEXT("Vector");
case EMaterialParameterType::DoubleVector: return TEXT("DoubleVector");
case EMaterialParameterType::Texture: return TEXT("Texture");
case EMaterialParameterType::TextureCollection: return TEXT("TextureCollection");
case EMaterialParameterType::Font: return TEXT("Font");
case EMaterialParameterType::RuntimeVirtualTexture: return TEXT("RuntimeVirtualTexture");
case EMaterialParameterType::SparseVolumeTexture: return TEXT("SparseVolumeTexture");
case EMaterialParameterType::StaticSwitch: return TEXT("StaticSwitch");
case EMaterialParameterType::StaticComponentMask: return TEXT("StaticComponentMask");
case EMaterialParameterType::Num: return TEXT("Num");
case EMaterialParameterType::None: return TEXT("None");
}
return TEXT("None");
}
bool WingMaterialParameter::EMaterialParameterTypeFromName(FName Name, EMaterialParameterType &Result, WingOut Errors)
{
if (Name == TEXT("Scalar")) { Result = EMaterialParameterType::Scalar; return true; }
if (Name == TEXT("Vector")) { Result = EMaterialParameterType::Vector; return true; }
if (Name == TEXT("DoubleVector")) { Result = EMaterialParameterType::DoubleVector; return true; }
if (Name == TEXT("Texture")) { Result = EMaterialParameterType::Texture; return true; }
if (Name == TEXT("TextureCollection")) { Result = EMaterialParameterType::TextureCollection; return true; }
if (Name == TEXT("Font")) { Result = EMaterialParameterType::Font; return true; }
if (Name == TEXT("RuntimeVirtualTexture")) { Result = EMaterialParameterType::RuntimeVirtualTexture; return true; }
if (Name == TEXT("SparseVolumeTexture")) { Result = EMaterialParameterType::SparseVolumeTexture; return true; }
if (Name == TEXT("StaticSwitch")) { Result = EMaterialParameterType::StaticSwitch; return true; }
if (Name == TEXT("StaticComponentMask")) { Result = EMaterialParameterType::StaticComponentMask; return true; }
if (Name == TEXT("Num")) { Result = EMaterialParameterType::Num; return true; }
if (Name == TEXT("None")) { Result = EMaterialParameterType::None; return true; }
Errors.Printf(TEXT("ERROR: '%s' is not a valid EMaterialParameterType\n"), *Name.ToString());
Result = EMaterialParameterType::None;
return false;
}
WingMaterialParameter::InfoMetaMap WingMaterialParameter::GetMaterialParameters(UMaterialInterface* Material)
{
InfoMetaMap Result;
if (!Material) return Result;
TMap<FMaterialParameterInfo, Metadata> Temp;
for (int I = 0; I < int(EMaterialParameterType::NumRuntime); I++)
{
EMaterialParameterType T = (EMaterialParameterType)I;
Material->GetAllParametersOfType(T, Temp);
for (const auto &KV : Temp)
{
check(KV.Value.Value.Type == T);
Result.Add(Info(KV.Key, T), KV.Value);
}
}
return Result;
}
void WingMaterialParameter::ReportBadToken(WingTokenizer &Tok, const TCHAR *Expected, WingOut Errors)
{
FString Whole = Tok.GetRange(FName(), 1000);
Tok.SaveCursor(FName());
FString Next = Tok.GetRange(FName(), 1);
Errors.Printf(TEXT("Parsing %s, near %s: expected %s"), *Whole, *Next, Expected);
}
FString WingMaterialParameter::StringID(const Info &ID)
{
const TCHAR *Type = EMaterialParameterTypeToString(ID.Type);
FString Ext = WingUtils::ExternalizeID(ID.Name);
if (ID.Association != EMaterialParameterAssociation::GlobalParameter)
{
const TCHAR *Prefix = TEXT("Layer");
if (ID.Association == EMaterialParameterAssociation::BlendParameter)
Prefix = TEXT("Blend");
return FString::Printf(TEXT("%s:%s:%d:%s"), Type, Prefix, ID.Index, *Ext);
}
else
{
return FString::Printf(TEXT("%s:%s"), Type, *Ext);
}
}
bool WingMaterialParameter::ParseID(const FString& IDString, Info& ID, WingOut Errors)
{
WingTokenizer Tok(IDString);
ID.Type = EMaterialParameterType::None;
ID.Association = EMaterialParameterAssociation::GlobalParameter;
ID.Index = 0;
ID.Name = FName();
// Parse the type.
const TCHAR *GoodTypes = TEXT("Vector, DoubleVector, Texture, TextureCollection, Font, RuntimeVirtualTexture, SparseVolumeTexture, or StaticSwitch");
if (!Tok.TokenIs(Tok.Identifier))
{ ReportBadToken(Tok, GoodTypes, Errors); return false; }
EMaterialParameterType TypeValue;
if (!EMaterialParameterTypeFromName(Tok.NextName(), TypeValue, Errors)) return false;
if (TypeValue >= EMaterialParameterType::NumRuntime)
{ ReportBadToken(Tok, GoodTypes, Errors); return false; }
ID.Type = TypeValue;
Tok.Advance();
// A colon after the type.
if (!Tok.TokenIs(':')) { ReportBadToken(Tok, TEXT("colon"), Errors); return false; }
// Parse the Layer or blend prefix.
if (Tok.TokenIs(TEXT("Layer"), ':') || Tok.TokenIs(TEXT("Blend"), ':'))
{
if (Tok.NextName() == TEXT("Layer"))
ID.Association = EMaterialParameterAssociation::LayerParameter;
else
ID.Association = EMaterialParameterAssociation::BlendParameter;
Tok.Advance();
Tok.Advance();
if ((!Tok.TokenIs(Tok.Identifier)) ||
(!LexTryParseString(ID.Index, *Tok.NextName().ToString())))
{ ReportBadToken(Tok, TEXT("integer"), Errors); return false; }
Tok.Advance();
if ((ID.Index < 0)||(ID.Index > 50))
{
Errors.Printf(TEXT("Layer/Blend index outside of reasonable range\n"));
return false;
}
if (!Tok.TokenIs(':'))
{ ReportBadToken(Tok, TEXT("colon"), Errors); return false; }
Tok.Advance();
}
// Parse the parameter name
if (!Tok.TokenIs(Tok.Identifier))
{ ReportBadToken(Tok, TEXT("parameter name"), Errors); return false; }
ID.Name = Tok.NextName();
Tok.Advance();
if (!Tok.TokenIs(0))
{ ReportBadToken(Tok, TEXT("end of input"), Errors); return false; }
return true;
}
WingMaterialParameter::Metadata *WingMaterialParameter::FindParameter(
InfoMetaMap &Map, const Info &ID, WingOut Errors)
{
Metadata* Found = Map.Find(ID);
if (!Found)
{
Errors.Printf(TEXT("No parameter '%s' in this material"), *StringID(ID));
return nullptr;
}
return Found;
}
bool WingMaterialParameter::CheckNoCustomPrimitiveData(
const Info &ID, const Metadata &Data, WingOut Errors)
{
if (Data.PrimitiveDataIndex != INDEX_NONE)
{
Errors.Printf(
TEXT("Parameter '%s' uses custom primitive data, which cannot be set on a material instance"),
*StringID(ID));
return false;
}
return true;
}
FString WingMaterialParameter::FormatValue(const FMaterialParameterValue& Value)
{
switch (Value.Type)
{
case EMaterialParameterType::Scalar: {
return FString::Printf(TEXT("%g"), Value.AsScalar());
}
case EMaterialParameterType::Vector: {
return Value.AsLinearColor().ToString();
}
case EMaterialParameterType::DoubleVector: {
return Value.AsVector4d().ToString();
}
case EMaterialParameterType::Texture: {
return PathNameOrNone(Value.Texture);
}
case EMaterialParameterType::TextureCollection: {
return PathNameOrNone(Value.TextureCollection);
}
case EMaterialParameterType::Font: {
return FString::Printf(TEXT("%s:%d"), *PathNameOrNone(Value.Font.Value), Value.Font.Page);
}
case EMaterialParameterType::RuntimeVirtualTexture: {
return PathNameOrNone(Value.RuntimeVirtualTexture);
}
case EMaterialParameterType::SparseVolumeTexture: {
return PathNameOrNone(Value.SparseVolumeTexture);
}
case EMaterialParameterType::StaticSwitch: {
return Value.AsStaticSwitch() ? TEXT("true") : TEXT("false");
}
case EMaterialParameterType::StaticComponentMask: {
const FStaticComponentMaskValue Mask = Value.AsStaticComponentMask();
return FString::Printf(
TEXT("(R=%s,G=%s,B=%s,A=%s)"),
Mask.R ? TEXT("true") : TEXT("false"),
Mask.G ? TEXT("true") : TEXT("false"),
Mask.B ? TEXT("true") : TEXT("false"),
Mask.A ? TEXT("true") : TEXT("false"));
}
case EMaterialParameterType::Num: {
return TEXT("invalid-parameter-type");
}
case EMaterialParameterType::None: {
return TEXT("invalid-parameter-type");
}
}
return TEXT("invalid-parameter-type");
}
bool WingMaterialParameter::AddOverride(
const Info &ID, UMaterialInstanceConstant *MI, const FString &Value, WingOut Errors)
{
switch (ID.Type)
{
case EMaterialParameterType::Scalar:
{
float ScalarValue;
if (!FDefaultValueHelper::ParseFloat(Value, ScalarValue))
{
Errors.Printf(TEXT("Failed to parse '%s' as a float"), *Value);
return false;
}
MI->SetScalarParameterValueEditorOnly(ID, ScalarValue);
return true;
}
case EMaterialParameterType::Vector:
{
FLinearColor Color;
if (!Color.InitFromString(Value))
{
Errors.Printf(TEXT("Failed to parse '%s' as a color/vector (expected format: '(R=1,G=0,B=0,A=1)')"), *Value);
return false;
}
MI->SetVectorParameterValueEditorOnly(ID, Color);
return true;
}
case EMaterialParameterType::DoubleVector:
case EMaterialParameterType::Texture:
case EMaterialParameterType::TextureCollection:
case EMaterialParameterType::Font:
case EMaterialParameterType::RuntimeVirtualTexture:
case EMaterialParameterType::SparseVolumeTexture:
case EMaterialParameterType::StaticSwitch:
case EMaterialParameterType::StaticComponentMask:
Errors.Printf(TEXT("Parameters of type %s are not implemented"), EMaterialParameterTypeToString(ID.Type));
return false;
case EMaterialParameterType::Num:
case EMaterialParameterType::None:
Errors.Printf(TEXT("Parameter Type is not valid"));
return false;
}
return false;
}
bool WingMaterialParameter::RemoveOverride(
const Info &ID, UMaterialInstanceConstant *MI, WingOut Errors)
{
// Remove the override from all parameter arrays.
auto RemoveFrom = [&](auto& Arr) {
return Arr.RemoveAll([&](auto& Entry) { return Entry.ParameterInfo == ID; });
};
int32 Removed = 0;
switch (ID.Type)
{
case EMaterialParameterType::Scalar: Removed += RemoveFrom(MI->ScalarParameterValues); break;
case EMaterialParameterType::Vector: Removed += RemoveFrom(MI->VectorParameterValues); break;
case EMaterialParameterType::DoubleVector: Removed += RemoveFrom(MI->DoubleVectorParameterValues); break;
case EMaterialParameterType::Texture: Removed += RemoveFrom(MI->TextureParameterValues); break;
case EMaterialParameterType::TextureCollection: Removed += RemoveFrom(MI->TextureCollectionParameterValues); break;
case EMaterialParameterType::Font: Removed += RemoveFrom(MI->FontParameterValues); break;
case EMaterialParameterType::RuntimeVirtualTexture: Removed += RemoveFrom(MI->RuntimeVirtualTextureParameterValues); break;
case EMaterialParameterType::SparseVolumeTexture: Removed += RemoveFrom(MI->SparseVolumeTextureParameterValues); break;
case EMaterialParameterType::StaticSwitch: Errors.Printf(TEXT("Removal of static switches not implemented")); return false;
case EMaterialParameterType::StaticComponentMask: Errors.Printf(TEXT("Removal of static component masks not implemented")); return false;
case EMaterialParameterType::Num: Errors.Printf(TEXT("Parameter Type is not valid")); return false;
case EMaterialParameterType::None: Errors.Printf(TEXT("Parameter Type is not valid")); return false;
}
if (Removed == 0)
{
Errors.Printf(TEXT("No override found for parameter '%s'"), *StringID(ID));
return false;
}
return true;
}
void WingMaterialParameter::Print(
const Info &Info, const Metadata &Meta)
{
WingOut::Stdout.Printf(TEXT(" %s %s\n"),
*WingMaterialParameter::StringID(Info), *WingMaterialParameter::FormatValue(Meta.Value));
}
void WingMaterialParameter::PrintAll(const InfoMetaMap &Params, bool Headings)
{
// Overridden parameters first.
bool bHasOverrides = false;
for (auto& [Info, Meta] : Params)
{
if (!Meta.bOverride) continue;
if (Headings && !bHasOverrides)
{ WingOut::Stdout.Print(TEXT("\nOverridden Parameters:\n")); bHasOverrides = true; }
WingMaterialParameter::Print(Info, Meta);
}
// Inherited (non-overridden) parameters.
bool bHasInherited = false;
for (auto& [Info, Meta] : Params)
{
if (Meta.bOverride) continue;
if (Headings && !bHasInherited)
{ WingOut::Stdout.Print(TEXT("\nInherited Parameters (not overridden):\n")); bHasInherited = true; }
WingMaterialParameter::Print(Info, Meta);
}
}

View File

@@ -0,0 +1,347 @@
#include "WingParameterEditor.h"
#include "WingUtils.h"
#include "WingTokenizer.h"
#include "WingProperty.h"
#include "WingServer.h"
#include "Engine/Font.h"
#include "Engine/TextureCollection.h"
#include "Misc/DefaultValueHelper.h"
#include "SparseVolumeTexture/SparseVolumeTexture.h"
#include "VT/RuntimeVirtualTexture.h"
FWingParameterEditor::GetterAndSetter::GetterAndSetter(FName N, GetterFunc G, SetterFunc S)
{
Property = FWingParameterEditor::StaticStruct()->FindPropertyByName(N);
Getter = G;
Setter = S;
}
FWingParameterEditor::GetterAndSetterMap FWingParameterEditor::CalcGetterAndSetterMap()
{
using Editor = FWingParameterEditor;
using Value = FMaterialParameterValue;
using Type = EMaterialParameterType;
GetterAndSetterMap M;
M.Add(Type::Scalar, GetterAndSetter(
GET_MEMBER_NAME_CHECKED(FWingParameterEditor, Scalar),
[](Editor &E)->Value { return Value(E.Scalar); },
[](Editor &E, const Value &V) { E.Scalar = V.AsScalar(); }));
M.Add(Type::Vector, GetterAndSetter(
GET_MEMBER_NAME_CHECKED(FWingParameterEditor, Vector),
[](Editor &E)->Value { return Value(E.Vector); },
[](Editor &E, const Value &V) { E.Vector = V.AsLinearColor(); }));
M.Add(Type::DoubleVector, GetterAndSetter(
GET_MEMBER_NAME_CHECKED(FWingParameterEditor, DoubleVector),
[](Editor &E)->Value { return Value(E.DoubleVector); },
[](Editor &E, const Value &V) { E.DoubleVector = V.AsVector4d(); }));
M.Add(Type::Texture, GetterAndSetter(
GET_MEMBER_NAME_CHECKED(FWingParameterEditor, Texture),
[](Editor &E)->Value { return Value(E.Texture); },
[](Editor &E, const Value &V) { E.Texture = V.Texture; }));
M.Add(Type::TextureCollection, GetterAndSetter(
GET_MEMBER_NAME_CHECKED(FWingParameterEditor, TextureCollection),
[](Editor &E)->Value { return Value(E.TextureCollection); },
[](Editor &E, const Value &V) { E.TextureCollection = V.TextureCollection; }));
M.Add(Type::Font, GetterAndSetter(
GET_MEMBER_NAME_CHECKED(FWingParameterEditor, Font),
[](Editor &E)->Value { return Value(E.Font.Font, E.Font.Page); },
[](Editor &E, const Value &V) { E.Font.Font = V.Font.Value; E.Font.Page = V.Font.Page; }));
M.Add(Type::RuntimeVirtualTexture, GetterAndSetter(
GET_MEMBER_NAME_CHECKED(FWingParameterEditor, RuntimeVirtualTexture),
[](Editor &E)->Value { return Value(E.RuntimeVirtualTexture); },
[](Editor &E, const Value &V) { E.RuntimeVirtualTexture = V.RuntimeVirtualTexture; }));
M.Add(Type::SparseVolumeTexture, GetterAndSetter(
GET_MEMBER_NAME_CHECKED(FWingParameterEditor, SparseVolumeTexture),
[](Editor &E)->Value { return Value(E.SparseVolumeTexture); },
[](Editor &E, const Value &V) { E.SparseVolumeTexture = V.SparseVolumeTexture; }));
M.Add(Type::StaticSwitch, GetterAndSetter(
GET_MEMBER_NAME_CHECKED(FWingParameterEditor, StaticSwitch),
[](Editor &E)->Value { return Value(E.StaticSwitch); },
[](Editor &E, const Value &V) { E.StaticSwitch = V.AsStaticSwitch(); }));
M.Add(Type::StaticComponentMask, GetterAndSetter(
GET_MEMBER_NAME_CHECKED(FWingParameterEditor, StaticComponentMask),
[](Editor &E)->Value { return Value(E.StaticComponentMask); },
[](Editor &E, const Value &V) { E.StaticComponentMask = V.AsStaticComponentMask(); }));
return M;
}
const FWingParameterEditor::GetterAndSetterMap &FWingParameterEditor::GetGetterAndSetterMap()
{
static GetterAndSetterMap TheMap = CalcGetterAndSetterMap();
return TheMap;
}
FName FWingParameterEditor::TypeToName(EMaterialParameterType Type)
{
const GetterAndSetter *GS = GetGetterAndSetterMap().Find(Type);
if (GS == nullptr) return FName(TEXT("unknown"));
return GS->Property->GetFName();
}
bool FWingParameterEditor::NameToType(FName Name, EMaterialParameterType &Result, WingOut Errors)
{
for (const auto &Pair : GetGetterAndSetterMap())
{
if (Pair.Value.Property->GetName() == Name)
{
Result = Pair.Key; return true;
}
}
Errors.Printf(TEXT("ERROR: '%s' is not a valid EMaterialParameterType\n"), *Name.ToString());
Result = EMaterialParameterType::None;
return false;
}
FWingParameterEditor::InfoMetaMap FWingParameterEditor::GetMaterialParameters(UMaterialInterface* Material)
{
InfoMetaMap Result;
if (!Material) return Result;
TMap<FMaterialParameterInfo, Metadata> Temp;
for (int I = 0; I < int(EMaterialParameterType::Num); I++)
{
EMaterialParameterType T = (EMaterialParameterType)I;
Material->GetAllParametersOfType(T, Temp);
for (const auto &KV : Temp)
{
check(KV.Value.Value.Type == T);
Result.Add(Info(KV.Key, T), KV.Value);
}
}
return Result;
}
bool FWingParameterEditor::ReportBadType(EMaterialParameterType Type, WingOut Errors)
{
Errors.Printf(TEXT("Material parameter type #%d is not a valid type.\n"));
return false;
}
void FWingParameterEditor::ReportBadToken(WingTokenizer &Tok, const TCHAR *Expected, WingOut Errors)
{
FString Whole = Tok.GetRange(FName(), 1000);
Tok.SaveCursor(FName());
FString Next = Tok.GetRange(FName(), 1);
Errors.Printf(TEXT("Parsing %s, near %s: expected %s"), *Whole, *Next, Expected);
}
FString FWingParameterEditor::StringID(const Info &ID)
{
FString TypeName = TypeToName(ID.Type).ToString();
FString Ext = WingUtils::ExternalizeID(ID.Name);
if (ID.Association != EMaterialParameterAssociation::GlobalParameter)
{
const TCHAR *Prefix = TEXT("Layer");
if (ID.Association == EMaterialParameterAssociation::BlendParameter)
Prefix = TEXT("Blend");
return FString::Printf(TEXT("%s:%s:%d:%s"), *TypeName, Prefix, ID.Index, *Ext);
}
else
{
return FString::Printf(TEXT("%s:%s"), *TypeName, *Ext);
}
}
bool FWingParameterEditor::ParseID(const FString& IDString, Info& ID, WingOut Errors)
{
WingTokenizer Tok(IDString);
ID.Type = EMaterialParameterType::None;
ID.Association = EMaterialParameterAssociation::GlobalParameter;
ID.Index = INDEX_NONE;
ID.Name = FName();
// Parse the type.
const TCHAR *GoodTypes = TEXT("Scalar, Vector, DoubleVector, Texture, TextureCollection, Font, "
"RuntimeVirtualTexture, SparseVolumeTexture, StaticSwitch, or StaticComponentMask");
if (!Tok.TokenIs(Tok.Identifier))
{ ReportBadToken(Tok, GoodTypes, Errors); return false; }
EMaterialParameterType TypeValue;
if (!NameToType(Tok.NextName(), TypeValue, Errors)) return false;
if (TypeValue >= EMaterialParameterType::Num)
{ ReportBadToken(Tok, GoodTypes, Errors); return false; }
ID.Type = TypeValue;
Tok.Advance();
// A colon after the type.
if (!Tok.TokenIs(':')) { ReportBadToken(Tok, TEXT("colon"), Errors); return false; }
Tok.Advance();
// Parse the Layer or blend prefix.
if (Tok.TokenIs(TEXT("Layer"), ':') || Tok.TokenIs(TEXT("Blend"), ':'))
{
if (Tok.NextName() == TEXT("Layer"))
ID.Association = EMaterialParameterAssociation::LayerParameter;
else
ID.Association = EMaterialParameterAssociation::BlendParameter;
Tok.Advance();
Tok.Advance();
if ((!Tok.TokenIs(Tok.Identifier)) ||
(!LexTryParseString(ID.Index, *Tok.NextName().ToString())))
{ ReportBadToken(Tok, TEXT("integer"), Errors); return false; }
Tok.Advance();
if ((ID.Index < 0)||(ID.Index > 50))
{
Errors.Printf(TEXT("Layer/Blend index outside of reasonable range\n"));
return false;
}
if (!Tok.TokenIs(':'))
{ ReportBadToken(Tok, TEXT("colon"), Errors); return false; }
Tok.Advance();
}
// Parse the parameter name
if (!Tok.TokenIs(Tok.Identifier))
{ ReportBadToken(Tok, TEXT("parameter name"), Errors); return false; }
ID.Name = Tok.NextName();
Tok.Advance();
if (!Tok.TokenIs(0))
{ ReportBadToken(Tok, TEXT("end of input"), Errors); return false; }
return true;
}
FWingParameterEditor::Metadata *FWingParameterEditor::FindParameter(
InfoMetaMap &Map, const Info &ID, WingOut Errors)
{
Metadata* Found = Map.Find(ID);
if (!Found)
{
Errors.Printf(TEXT("No parameter '%s' in this material"), *StringID(ID));
return nullptr;
}
return Found;
}
bool FWingParameterEditor::CheckNoCustomPrimitiveData(
const Info &ID, const Metadata &Data, WingOut Errors)
{
if (Data.PrimitiveDataIndex != INDEX_NONE)
{
Errors.Printf(
TEXT("Parameter '%s' uses custom primitive data, which cannot be set on a material instance"),
*StringID(ID));
return false;
}
return true;
}
bool FWingParameterEditor::AddOverride(
const Info &ID, UMaterialInstanceConstant *MI, const FString &StrVal, WingOut Errors)
{
// Verify that the parameter type is valid.
const GetterAndSetter *GS = GetGetterAndSetterMap().Find(ID.Type);
if (GS == nullptr) return ReportBadType(ID.Type, Errors);
// Find the existing parameter.
TMap<FMaterialParameterInfo, FMaterialParameterMetadata> Lookup;
MI->GetAllParametersOfType(ID.Type, Lookup);
FMaterialParameterMetadata *Meta = Lookup.Find(ID);
if (Meta == nullptr)
{
Errors.Printf(TEXT("No parameter named %s to override.\n"), *StringID(ID));
return false;
}
check(Meta->Value.Type == ID.Type);
// If the parameter involves the use of custom primitive data, we
// cannot meaningfully override it.
if (!CheckNoCustomPrimitiveData(ID, *Meta, Errors)) return false;
// Parse the value string.
FWingParameterEditor Editor;
if (!FWingProperty(GS->Property, &Editor, true).SetText(StrVal, Errors)) return false;
Meta->Value = GS->Getter(Editor);
// Apply the update.
FMaterialInstanceParameterUpdateContext Update(MI);
Update.SetParameterValueEditorOnly(ID, *Meta);
return true;
}
bool FWingParameterEditor::RemoveOverride(
const Info &ID, UMaterialInstanceConstant *MI, WingOut Errors)
{
// Remove the override from all parameter arrays.
auto RemoveFrom = [&](auto& Arr) {
return Arr.RemoveAll([&](auto& Entry) { return Entry.ParameterInfo == ID; });
};
int32 Removed = 0;
switch (ID.Type)
{
case EMaterialParameterType::Scalar: Removed += RemoveFrom(MI->ScalarParameterValues); break;
case EMaterialParameterType::Vector: Removed += RemoveFrom(MI->VectorParameterValues); break;
case EMaterialParameterType::DoubleVector: Removed += RemoveFrom(MI->DoubleVectorParameterValues); break;
case EMaterialParameterType::Texture: Removed += RemoveFrom(MI->TextureParameterValues); break;
case EMaterialParameterType::TextureCollection: Removed += RemoveFrom(MI->TextureCollectionParameterValues); break;
case EMaterialParameterType::Font: Removed += RemoveFrom(MI->FontParameterValues); break;
case EMaterialParameterType::RuntimeVirtualTexture: Removed += RemoveFrom(MI->RuntimeVirtualTextureParameterValues); break;
case EMaterialParameterType::SparseVolumeTexture: Removed += RemoveFrom(MI->SparseVolumeTextureParameterValues); break;
case EMaterialParameterType::StaticSwitch: Errors.Printf(TEXT("Removal of static switches not implemented")); return false;
case EMaterialParameterType::StaticComponentMask: Errors.Printf(TEXT("Removal of static component masks not implemented")); return false;
case EMaterialParameterType::Num: Errors.Printf(TEXT("Parameter Type is not valid")); return false;
case EMaterialParameterType::None: Errors.Printf(TEXT("Parameter Type is not valid")); return false;
}
if (Removed == 0)
{
Errors.Printf(TEXT("No override found for parameter '%s'"), *StringID(ID));
return false;
}
return true;
}
void FWingParameterEditor::Print(const Info &ID, const Metadata &Meta)
{
const GetterAndSetter *GS = GetGetterAndSetterMap().Find(ID.Type);
if ((GS == nullptr) || (ID.Type != Meta.Value.Type))
{
WingOut::Stdout.Printf(TEXT(" corrupted parameter: %s\n"),
*StringID(ID));
return;
}
FWingParameterEditor Editor;
GS->Setter(Editor, Meta.Value);
FString StrVal = FWingProperty(GS->Property, &Editor, false).GetText();
WingOut::Stdout.Printf(TEXT(" %s %s\n"),
*StringID(ID), *StrVal);
}
void FWingParameterEditor::PrintAll(const InfoMetaMap &Params, bool Headings)
{
// Overridden parameters first.
bool bHasOverrides = false;
for (auto& [Info, Meta] : Params)
{
if (!Meta.bOverride) continue;
if (Headings && !bHasOverrides)
{ WingOut::Stdout.Print(TEXT("\nOverridden Parameters:\n")); bHasOverrides = true; }
FWingParameterEditor::Print(Info, Meta);
}
// Inherited (non-overridden) parameters.
bool bHasInherited = false;
for (auto& [Info, Meta] : Params)
{
if (Meta.bOverride) continue;
if (Headings && !bHasInherited)
{ WingOut::Stdout.Print(TEXT("\nInherited Parameters (not overridden):\n")); bHasInherited = true; }
FWingParameterEditor::Print(Info, Meta);
}
}

View File

@@ -5,11 +5,61 @@
#include "WingTokenizer.h"
#include "Materials/MaterialInstanceConstant.h"
#include "WingHandler.h"
#include "WingParameterEditor.generated.h"
class WingMaterialParameter
USTRUCT()
struct FWingFontAndPage
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere)
UFont *Font;
UPROPERTY(EditAnywhere)
int32 Page;
};
// FWingParameterEditor is a tool for modifying material
// parameters in material instances. Transferring values
// into UPROPERTY fields makes it possible to use GetText
// and SetText to parse them.
//
USTRUCT()
struct FWingParameterEditor
{
GENERATED_BODY()
private:
UPROPERTY(EditAnywhere)
float Scalar;
UPROPERTY(EditAnywhere)
FLinearColor Vector;
UPROPERTY(EditAnywhere)
FVector4d DoubleVector;
UPROPERTY(EditAnywhere)
UTexture *Texture;
UPROPERTY(EditAnywhere)
UTextureCollection *TextureCollection;
UPROPERTY(EditAnywhere)
FWingFontAndPage Font;
UPROPERTY(EditAnywhere)
URuntimeVirtualTexture *RuntimeVirtualTexture;
UPROPERTY(EditAnywhere)
USparseVolumeTexture *SparseVolumeTexture;
UPROPERTY(EditAnywhere)
bool StaticSwitch;
UPROPERTY(EditAnywhere)
FStaticComponentMaskValue StaticComponentMask;
public:
// Info struct is a FMaterialParameterInfo plus a type field.
struct Info : public FMaterialParameterInfo
@@ -32,12 +82,17 @@ public:
}
};
using Type = EMaterialParameterType;
using Metadata = FMaterialParameterMetadata;
using Value = FMaterialParameterValue;
using InfoMetaMap = TMap<Info, Metadata>;
// Convert a type to a name.
//
static FName TypeToName(EMaterialParameterType Type);
// Convert a name to a type, if possible.
//
static bool NameToType(FName Name, EMaterialParameterType &Result, WingOut Errors);
// Get all the material parameters of the specified material including
// both parameters that have been overridden and those that haven't.
//
@@ -48,7 +103,7 @@ public:
//
static Metadata *FindParameter(InfoMetaMap &Map, const Info &ID, WingOut Errors);
// If the material uses custom primitive data, print an error
// If the material uses custom primitive data, print an error
// and return false.
//
static bool CheckNoCustomPrimitiveData(const Info &ID, const Metadata &Data, WingOut Errors);
@@ -64,17 +119,13 @@ public:
// Add a material parameter override to a material instance.
//
static bool AddOverride(
const Info &ID, UMaterialInstanceConstant *MI, const FString &Value, WingOut Errors);
const Info &ID, UMaterialInstanceConstant *MI, const FString &StrVal, WingOut Errors);
// Remove a material parameter override from a material instance.
// If there is no override, print an error and return false.
//
static bool RemoveOverride(const Info &ID, UMaterialInstanceConstant *MI, WingOut Errors);
// Format a material parameter value.
//
static FString FormatValue(const Value& Value);
// Print out a material parameter with ID.
//
static void Print(const Info &Info, const Metadata &Meta);
@@ -84,10 +135,28 @@ public:
//
static void PrintAll(const InfoMetaMap &Params, bool Headings);
private:
using GetterFunc = TFunction<FMaterialParameterValue(FWingParameterEditor &E)>;
using SetterFunc = TFunction<void(FWingParameterEditor &E, const FMaterialParameterValue &V)>;
struct GetterAndSetter
{
FProperty *Property;
GetterFunc Getter;
SetterFunc Setter;
GetterAndSetter(FName N, GetterFunc G, SetterFunc S);
};
using GetterAndSetterMap = TMap<EMaterialParameterType, GetterAndSetter>;
static void Add(GetterAndSetterMap &M,
EMaterialParameterType Type, FName Name, GetterFunc G, SetterFunc S);
static GetterAndSetterMap CalcGetterAndSetterMap();
static const GetterAndSetterMap &GetGetterAndSetterMap();
private:
static void ReportBadToken(WingTokenizer &Tok, const TCHAR *Expected, WingOut Errors);
static const TCHAR *EMaterialParameterTypeToString(EMaterialParameterType Type);
static bool EMaterialParameterTypeFromName(FName Name, EMaterialParameterType &Result, WingOut Errors);
static FString PathNameOrNone(UObject *Obj) { return Obj ? Obj->GetPathName() : FString(TEXT("None")); }
static bool ReportBadType(EMaterialParameterType Type, WingOut Errors);
};