PCG [UE5.3] Adding "Get Attributes" to World Ray Hit Query

Using the latest version of PCG in UE5.3 I noticed that there is no way to get the VertexColor values from the World Ray Hit Query and this generates a white color attribute (1,1,1,1) for each point with the Surface Sampler.

Is it possibile to add this functionality to get not only the VertexColor but also other attributes like UV Channels? This can be really helpful :pray:t2:

Thanks
Simone Marrocco

Up

Even in c++ I’m having a difficult time solving how to get the Attribute values for points. I can get the attribute easily enough, but in debugging I can’t find where the actual value is stored…

I rescind my previous statement, I figured it out. if you have experience with c++, here’s how to access values of attributes. This is a custom PCGSettings node I created based off PCGDistanceSettings:

FPCGAsync::AsyncPointProcessing(Context, SourcePointData->GetPoints(), OutputData->GetMutablePoints(),
	[OutputData, OwnerBP, GridPositionAttribute, ModifierAttribute, /*SourceShape, SourceShape,*/ &SourcePointDatas/*, MaximumDistance, ScalarAttribute, VectorAttribute, bSetDensity*/](const FPCGPoint& SourcePoint, FPCGPoint& OutPoint) {

		OutPoint = SourcePoint;

/// OutPoint is the actual point, and the MetaDataEntry is just an int64 identifier
/// GetValue() asks for PCGMetadataValueKey but that is just a typedef for int32
		FVector gridPosition = GridPositionAttribute->GetValue(OutPoint.MetadataEntry);
}
};

Header:

// Copyright Jordan Cain. All Rights Reserved.


#pragma once

#include "PCGSettings.h"
#include "Elements/PCGPointProcessingElementBase.h"
#include "PCG/PCGBuildingBlueprint.h"
#include "PCGAssignModifierTagsToPoints.generated.h"


namespace PCGAssignModifierTagsToPoints
{
	extern const FName SourceLabel;
	//extern const FName TargetLabel;
}

/**
 * Assigns the provided tag to a point whose grid position falls within the provided reqs for that tag
 */
UCLASS(BlueprintType, ClassGroup = (Procedural))
class UPCGAssignModifierTagsToPointsSettings : public UPCGSettings
{
	GENERATED_BODY()

public:
	//~Begin UPCGSettings interface
#if WITH_EDITOR
	virtual FName GetDefaultNodeName() const override { return FName(TEXT("AssignModifierTagsToPoints")); }
	virtual FText GetDefaultNodeTitle() const override { return NSLOCTEXT("PCGAssignModifierTagsToPointsSettings", "NodeTitle", "AssignModifierTagsToPoints"); }
	virtual FText GetNodeTooltipText() const override;
	virtual EPCGSettingsType GetType() const override { return EPCGSettingsType::Spatial; }
#endif

	virtual TArray<FPCGPinProperties> InputPinProperties() const override;
	virtual TArray<FPCGPinProperties> OutputPinProperties() const override;

protected:
	virtual FPCGElementPtr CreateElement() const override;
	//~End UPCGSettings interface

public:

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Settings, meta = (PCG_Overridable))
	/// As we cannot pass in custom structs nor arrays of structs as attribute parameter, we'll just access the bp variables directly
	TSoftObjectPtr<APCGBuildingBlueprint> OwnerBlueprint = nullptr;

};

class FPCGAssignModifierTagsElement : public FPCGPointProcessingElementBase
{
protected:
	virtual bool ExecuteInternal(FPCGContext* Context) const override;
};


#if UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_2
#include "CoreMinimal.h"
#endif

CPP:

// Copyright Jordan Cain. All Rights Reserved.

#include "PCG/PCGAssignModifierTagsToPoints.h"
#include "Data/PCGSpatialData.h"
#include "Helpers/PCGAsync.h"
#include "Data/PCGPointData.h"
#include "PCGContext.h"
#include "PCGPin.h"
#include "Kismet/KismetMathLibrary.h"
#include "GlobalLog.h"

#define LOCTEXT_NAMESPACE "PCGAssignModifierTagsElement"

namespace PCGAssignModifierTagsToPoints
{
	const FName SourceLabel = TEXT("Source");
}

#if WITH_EDITOR
FText UPCGAssignModifierTagsToPointsSettings::GetNodeTooltipText() const
{
	return LOCTEXT("PCGDistanceTooltip", "Calculates and appends a signed 'GridPosition' attribute to the source data. For each of the source points, a GridPosition attribute will be calculated against origin of points.");
}
#endif // WITH_EDITOR

TArray<FPCGPinProperties> UPCGAssignModifierTagsToPointsSettings::InputPinProperties() const
{
	TArray<FPCGPinProperties> PinProperties;
	FPCGPinProperties& PinPropertySource = PinProperties.Emplace_GetRef(PCGAssignModifierTagsToPoints::SourceLabel, EPCGDataType::Point);

#if WITH_EDITOR
	PinPropertySource.Tooltip = LOCTEXT("PCGSourcePinTooltip", "For each of the source points, a grid position attribute will be calculated against origin of points.");
#endif // WITH_EDITOR

	return PinProperties;
}

TArray<FPCGPinProperties> UPCGAssignModifierTagsToPointsSettings::OutputPinProperties() const
{
	TArray<FPCGPinProperties> PinProperties;
	FPCGPinProperties& PinPropertyOutput = PinProperties.Emplace_GetRef(PCGPinConstants::DefaultOutputLabel, EPCGDataType::Point);

#if WITH_EDITOR
	PinPropertyOutput.Tooltip = LOCTEXT("PCGOutputPinTooltip", "The source points will be output with the newly added 'GridPosition' attribute.");
#endif // WITH_EDITOR

	return PinProperties;
}

FPCGElementPtr UPCGAssignModifierTagsToPointsSettings::CreateElement() const
{
	return MakeShared<FPCGAssignModifierTagsElement>();
}

bool FPCGAssignModifierTagsElement::ExecuteInternal(FPCGContext* Context) const
{
	TRACE_CPUPROFILER_EVENT_SCOPE(FPCGDistanceElement::Execute);

	const UPCGAssignModifierTagsToPointsSettings* Settings = Context->GetInputSettings<UPCGAssignModifierTagsToPointsSettings>();
	check(Settings);
	check(Settings->OwnerBlueprint);

	TSoftObjectPtr<APCGBuildingBlueprint> OwnerBP = Settings->OwnerBlueprint;
	//const FName AttributeName = Settings->AttributeName;
	//FVector WorldLocationOfOwner = Settings->WorldLocationOfOwner;
	//FRotator RotationOfOwner = Settings->RotationOfOwner;
	//float WallSize = Settings->WallSize;

	TArray<FPCGTaggedData> Sources = Context->InputData.GetInputsByPin(PCGAssignModifierTagsToPoints::SourceLabel);
	TArray<FPCGTaggedData>& Outputs = Context->OutputData.TaggedData;

	TArray<const UPCGPointData*> SourcePointDatas;
	SourcePointDatas.Reserve(Sources.Num());

	for (const FPCGTaggedData& Source : Sources)
	{
		const UPCGSpatialData* SourceData = Cast<UPCGSpatialData>(Source.Data);

		if (!SourceData)
		{
			PCGE_LOG(Error, GraphAndLog, FText::Format(LOCTEXT("SourceMustBeSpatial", "Source must be Spatial data, found '{0}'"), FText::FromString(Source.Data->GetClass()->GetName())));
			continue;
		}

		const UPCGPointData* SourcePointData = SourceData->ToPointData(Context);
		if (!SourcePointData)
		{
			PCGE_LOG(Error, GraphAndLog, FText::Format(LOCTEXT("CannotConvertToPoint", "Cannot convert target '{0}' into Point data"), FText::FromString(Source.Data->GetClass()->GetName())));
			continue;
		}

		SourcePointDatas.Add(SourcePointData);
	}

	for (const FPCGTaggedData& Source : Sources)
	{
		const UPCGSpatialData* SourceData = Cast<UPCGSpatialData>(Source.Data);

		if (!SourceData)
		{
			PCGE_LOG(Error, GraphAndLog, LOCTEXT("InvalidInputData", "Invalid input data"));
			continue;
		}

		const UPCGPointData* SourcePointData = SourceData->ToPointData(Context);
		if (!SourcePointData)
		{
			PCGE_LOG(Error, GraphAndLog, LOCTEXT("CannotConvertToPointData", "Cannot convert input Spatial data to Point data"));
			continue;
		}

		UPCGPointData* OutputData = NewObject<UPCGPointData>();
		OutputData->InitializeFromData(SourcePointData);
		Outputs.Add_GetRef(Source).Data = OutputData;
		
		const FPCGMetadataAttribute<FVector>* GridPositionAttribute = OutputData->Metadata->GetConstTypedAttribute<FVector>(FName("GridPosition"));

		FPCGMetadataAttribute<FName>* ModifierAttribute = OutputData->Metadata->FindOrCreateAttribute<FName>(FName("ModifierTag"), FName("NoModifier"));

		check(GridPositionAttribute);
		check(ModifierAttribute);

		FPCGAsync::AsyncPointProcessing(Context, SourcePointData->GetPoints(), OutputData->GetMutablePoints(),
			[OutputData, OwnerBP, GridPositionAttribute, ModifierAttribute, /*SourceShape, SourceShape,*/ &SourcePointDatas/*, MaximumDistance, ScalarAttribute, VectorAttribute, bSetDensity*/](const FPCGPoint& SourcePoint, FPCGPoint& OutPoint) {

				OutPoint = SourcePoint;
				OutPoint.MetadataEntry;

				FName modifierTag = "NoModifier";

				bool bMeetsModifierCriteria = false;

				FVector gridPosition = GridPositionAttribute->GetValue(OutPoint.MetadataEntry);

				for (FPCGModifierTagReqs& criterion : OwnerBP->modifierConditions)
				{
					bMeetsModifierCriteria = criterion.PointMeetsCriteria(gridPosition);

					if (bMeetsModifierCriteria) { break; }/// We can break at first true criterion
					/// Todo: PCG; What if a point can meet 2 criteria?
				}

				if (bMeetsModifierCriteria)
				{
					if (ModifierAttribute)
					{
						OutputData->Metadata->InitializeOnSet(OutPoint.MetadataEntry);
						ModifierAttribute->SetValue(OutPoint.MetadataEntry, modifierTag);
					}
					else
					{
						Global::LogError(LogPCG, "UPCGAssignModifierTagsToPointsSettings", "ExecuteInternal", TEXT("Modifier: %s"), ModifierAttribute ? TEXT("Is Valid.") : TEXT("Is Not Valid"));
					}
				}

				return true;
			}
		);
	}

	return true;
}

#undef LOCTEXT_NAMESPACE

2 Likes

Can you explain how one should go about creating a custom PCG node, If I just try to create a class based on UPCGSettings, it won’t show up in the editor, do we need to register it somewhere?

If you have some reference/documentation on how you figured it out, I would be happy to know about it as well.