Skip to content

Attribute System ​

PPG's attribute system is the mechanism by which per-point data beyond position and rotation is stored, accessed, and passed between nodes. It has three layers:

  1. PPGMetadata — the actual storage (column-oriented, typed attribute arrays).
  2. PPGAttributeSelector — a serializable, type-safe reference to either a built-in point property or a named metadata attribute.
  3. PPGAttributeAccessor — static helpers that dispatch reads/writes through a PPGAttributeSelector.

Two interfaces, IPPGAttributeProvider and IPPGAttributeConsumer, let nodes declare at the Settings level which attributes they produce or consume.


PPGMetadata ​

Each PPGPointData owns a PPGMetadata instance. Metadata is column-oriented: each named attribute is a typed column, and each point holds an long entry key that indexes into those columns.

Creating and populating metadata ​

csharp
var metadata = new PPGMetadata();

// Create typed attribute columns
metadata.CreateAttribute<float>("Density");
metadata.CreateAttribute<Unity.Mathematics.float3>("WindDirection");
metadata.CreateAttribute<int>("VariantIndex");

// Allocate an entry for each point and set values
for (var i = 0; i < points.Length; i++)
{
    var entryKey = metadata.AddEntry();   // returns a long key
    points[i].metadataEntry = entryKey;  // link the point to its entry

    metadata.SetValue(entryKey, "Density",       rng.NextFloat());
    metadata.SetValue(entryKey, "WindDirection",  new float3(1, 0, 0));
    metadata.SetValue(entryKey, "VariantIndex",   rng.NextInt(0, 4));
}

Reading metadata directly ​

csharp
if (metadata.HasAttribute("Density"))
{
    var density = metadata.GetValue<float>(point.metadataEntry, "Density");
    Debug.Log($"Density: {density}");
}

// Check attribute type before reading
var attrType = metadata.GetAttributeType("WindDirection");  // returns System.Type

Key methods ​

MethodDescription
AddEntry()Allocates a new entry key (long). Call once per point.
CreateAttribute<T>(name, defaultValue?)Creates a new typed column. No-op if the name already exists.
HasAttribute(name)Returns true if the named column exists.
GetAttributeType(name)Returns the System.Type of the named column, or null.
GetAttributeTypeEnum(name)Returns the PPGAttributeType enum value of the named column.
SetValue<T>(entryKey, name, value)Writes a value to the specified entry. Creates the column if it does not exist.
GetValue<T>(entryKey, name)Reads a value from the specified entry. Returns default(T) if not found.
Clone()Deep-copies the metadata, including all columns and all entry values.

PPGAttributeSelector ​

PPGAttributeSelector is a serializable struct used in node Settings classes wherever a user-selectable attribute reference is needed. It encodes three things in one struct:

  • Whether the target is a built-in PPGPointProperty (Position, Rotation, Scale, Density, …) or a custom metadata attribute name.
  • An optional component index for vector properties (e.g., Position.X → index 0).
  • Validation logic (IsValid, IsPointProperty, IsCustomAttribute).

Creating selectors in code ​

csharp
// Built-in property (e.g., Density)
var selector = PPGAttributeSelector.BuiltIn(PPGPointProperty.Density);

// Sub-component of a built-in property (Position.X)
var xSelector = PPGAttributeSelector.BuiltInComponent(PPGPointProperty.Position, 0);

// Custom metadata attribute
var customSelector = PPGAttributeSelector.Custom("WindDirection");

// Sub-component of a custom attribute (WindDirection.Y)
var windY = PPGAttributeSelector.CustomComponent("WindDirection", 1);

// Parse from a legacy string name (e.g., from older serialized data)
var legacySelector = PPGAttributeSelector.FromLegacyName("Position.X");

Component index mapping ​

IndexXYZ aliasRGBA alias
0XR
1YG
2ZB
3WA

Selector properties ​

PropertyTypeDescription
IsValidbooltrue if the selector has a usable target.
IsPointPropertybooltrue if targeting a built-in PPGPointProperty.
IsCustomAttributebooltrue if targeting a named metadata column.
HasComponentbooltrue if a sub-component index is set.
ComponentIndexintSub-component index (0–3). Meaningful only when HasComponent is true.
PointPropertyPPGPointPropertyThe built-in property, or None for custom attributes.
CustomNamestringThe metadata attribute name, or null for built-in properties.
DisplayNamestringHuman-readable label (e.g., "Position.X", "WindDirection").
ResolvedTypePPGAttributeTypeReturns Float when a sub-component is selected; otherwise the natural attribute type.

Using a selector in a Settings class ​

csharp
[Serializable]
[PPGNodeInfo("Remap Attribute", "Attributes", "Remaps one attribute's value range")]
[PPGInputPort("In",  PPGDataType.Point, Required = true)]
[PPGOutputPort("Out", PPGDataType.Point)]
[PPGElement(typeof(RemapAttributeElement))]
public sealed class RemapAttributeSettings : PPGSettings
{
    // Shown as a dropdown in the Inspector — picks from upstream attributes
    public PPGAttributeSelector SourceAttribute = PPGAttributeSelector.BuiltIn(PPGPointProperty.Density);
    public float InputMin  = 0f;
    public float InputMax  = 1f;
    public float OutputMin = 0f;
    public float OutputMax = 1f;
}

PPGAttributeAccessor ​

PPGAttributeAccessor is a static class that provides unified reads and writes through a PPGAttributeSelector. Use it inside Element Execute methods instead of writing your own dispatch logic.

Reading float values ​

csharp
// Inside IPPGElement.Execute:
var settings = context.GetSettings<RemapAttributeSettings>();
var input    = context.GetFirstInputPointData("In");
var output   = new PPGPointData { Metadata = input.Metadata?.Clone() };

var points = input.Points.AsArray();
for (var i = 0; i < points.Length; i++)
{
    var point = points[i];

    // Read from built-in or custom attribute transparently
    var raw     = PPGAttributeAccessor.ReadFloat(settings.SourceAttribute, point, input.Metadata);
    var remapped = Mathf.Lerp(settings.OutputMin, settings.OutputMax,
                              Mathf.InverseLerp(settings.InputMin, settings.InputMax, raw));

    // Write back — WriteFloat returns the mutated point struct
    point = PPGAttributeAccessor.WriteFloat(settings.SourceAttribute, point, output.Metadata, remapped);
    output.Points.Add(point);
}

context.AddOutputData("Out", output);

Reading int values ​

csharp
var variantIndex = PPGAttributeAccessor.ReadInt(selector, point, metadata);

Type coercion rules ​

PPGAttributeAccessor coerces between attribute types automatically:

Source typeReadFloat resultReadInt result
Floatdirectcast to int
Intcast to floatdirect
Bool1f / 0f1 / 0
Float2/3/4 (no component).x component(int).x component
Float2/3/4 (with component)indexed component(int) indexed component
Quaternion (no component).value.x(int).value.x
Quaternion (with component).value[index](int).value[index]
String0f0

When the selector points to a built-in property, the read dispatches directly to the PPGPoint struct field — no metadata lookup occurs.


IPPGAttributeProvider ​

Implement this interface on your Settings class when your node creates custom metadata attributes. The editor uses the information to populate the attribute selector dropdown for downstream nodes.

csharp
[Serializable]
[PPGNodeInfo("Tag By Slope", "Attributes", "Adds a SlopeFactor float attribute to each point")]
[PPGInputPort("In",  PPGDataType.Point)]
[PPGOutputPort("Out", PPGDataType.Point)]
[PPGElement(typeof(TagBySlopeElement))]
public sealed class TagBySlopeSettings : PPGSettings, IPPGAttributeProvider
{
    public string AttributeName = "SlopeFactor";

    public IEnumerable<(string name, PPGAttributeType type)> GetProvidedAttributes()
    {
        yield return (this.AttributeName, PPGAttributeType.Float);
    }
}

The interface has a single method:

csharp
IEnumerable<(string name, PPGAttributeType type)> GetProvidedAttributes();

Return one tuple per attribute column your node writes to the output metadata.


IPPGAttributeConsumer ​

Implement this interface on your Settings class when your node reads custom metadata attributes by name. The graph validator uses this information to warn when a referenced attribute does not exist in any upstream IPPGAttributeProvider.

csharp
public sealed class RemapAttributeSettings : PPGSettings,
    IPPGAttributeConsumer
{
    public PPGAttributeSelector SourceAttribute;

    public IEnumerable<string> GetConsumedCustomAttributes()
    {
        // Only report custom attributes; built-in properties are always available
        if (this.SourceAttribute.IsCustomAttribute)
            yield return this.SourceAttribute.CustomName;
    }
}

The interface has a single method:

csharp
IEnumerable<string> GetConsumedCustomAttributes();

Return the names of all metadata columns your node reads. Do not include built-in PPGPointProperty names here.


Combining Provider and Consumer ​

A node can implement both interfaces — for example, a node that reads one attribute and writes a derived attribute:

csharp
public sealed class NormalizeAttributeSettings : PPGSettings,
    IPPGAttributeProvider,
    IPPGAttributeConsumer
{
    public string SourceName = "RawWeight";
    public string OutputName = "NormalizedWeight";

    public IEnumerable<(string, PPGAttributeType)> GetProvidedAttributes()
    {
        yield return (this.OutputName, PPGAttributeType.Float);
    }

    public IEnumerable<string> GetConsumedCustomAttributes()
    {
        yield return this.SourceName;
    }
}

PPGAttributeType Enum ​

ValueC# TypeDescription
FloatfloatSingle-precision scalar
IntintInteger scalar
BoolboolBoolean
Float2Unity.Mathematics.float22-component vector
Float3Unity.Mathematics.float33-component vector
Float4Unity.Mathematics.float44-component vector / color
QuaternionUnity.Mathematics.quaternionRotation
StringstringArbitrary text

Procedural Placement Graph for Unity