Expression attributes
The @ values read and written inside an Expression. Which ones exist depends on the input.
An Expression runs once per element of whatever is wired into it. An @name is one of those per-element values: a point's position, a pixel's colour, a voxel's density. Read them to make decisions, assign to them to change the result.
// read @Cd, write @P.z: push each point up by its brightness
@P.z = luminance(@Cd) * 100.0;
The input sets the loop
Points. Once per point, with
@P,@Cd,@N,@pscale,@id,@ptnum,@numpt.Mesh. Once per vertex, with
@P,@N,@uv.Analytic shapes. Once per shape, with
@P,@analytic(which shape it is) and@dims0/@dims1(its dimensions).Gaussian splats. Once per splat, with
@P,@scale,@rot,@opacity.Image (raster). Once per pixel.
@Cdis the pixel colour, and@Pand@uvare both that pixel's UV.Number array. Once per number.
@valueis the number itself, so@value = @value * 2.0scales the whole list.Volume. Once per voxel.
@Pis the world position,@densitythe value at that voxel.Distance Field. Once per sample of a regular grid over the field's bounds.
@Pis the sample position and@distthe distance to the surface.
There is no mode to pick. It follows the input, and Automatic conversion covers the conversions between these kinds.
An image cooks as an image and a Volume cooks as a Volume, so a snippet on either one keeps the representation it was given. A distance field has no grid of its own, so it resolves to points, one per grid sample.
The common attributes
Vectors support swizzles, so @P.z, @Cd.rgb and @uv.x all work.
| Attribute | What it is | Where it lives |
|---|---|---|
@P | Position | points, mesh, volume, distance field |
@Cd | Colour (RGBA) | points, image |
@Cd1, @Cd2, @Cd3 | The colour at this pixel in the second, third and fourth wired image | image |
@value | The number itself | number array |
@N | Normal direction | points, mesh |
@pscale | Per-element scale / size | points |
@uv | Texture coordinate | image, mesh |
@density | Voxel value | volume |
@dist | Distance to surface | distance field |
@analytic | Which analytic shape this row is | analytic shapes |
@dims0, @dims1 | That shape's dimensions, four numbers each | analytic shapes |
@id | Stable identity that survives across frames | points, mesh, analytic shapes, splats |
@ptnum | The element's index in the loop | every collection |
@numpt | How many elements the loop runs over | every collection |
@dist, @analytic, @dims0, @dims1, @rot and @opacity are columns the data carries and every node downstream can use, but the Expression language does not yet know them by name: a bare read of one is a compile error.
An image Expression takes up to four images. @Cd is the one on the first input and @Cd1, @Cd2 and @Cd3 are the second, third and fourth, which is how a clean plate, a guide image or a displacement map is read alongside the picture being changed. The pins carrying them are labelled input2, input3 and input4, so the number on the pin is always one higher than the number on the attribute. Fed anything other than an image, @Cd1 is an ordinary colour column that the input has to carry under that exact name.
Attributes covers how attributes attach to data across the app.
Reading and writing
Reading is mentioning the attribute. Writing is assigning to it on the left of an =.
@Cd = @P.y > 0 ? {1, 0, 0, 1} : {0, 0, 1, 1}; // read @P, write @Cd
@P += @N * noise(@P * 0.1 + $T) * 5.0; // read @N and @P, write @P
@ptnum and @numpt come from the loop rather than from a stored column, so they are available on any collection whatever else it carries. Everything else in the table is a column, present only when the input actually has it.
@id can be written, and it is the handle that keeps a point recognisable from one frame to the next. Simulations and effects depend on it, so overwrite it only deliberately.
A swizzled write keeps the rest of the vector. @P.z = ... changes Z alone.
The table above is most of the vocabulary the language knows by name. The rest is @v (velocity), @Alpha, @scale, @orient, @rotation, @weight, @sdf, the simulation pair @age and @life, and the loop stamps @index, @total and @normalized. Any other name has to be written before it is read: @wobble = noise(@P); makes a new attribute and later lines can read it back, but a bare read of a name the snippet never assigned is a compile error.
Reading a column that might not be there
attr_or(default, "name") is the optional read. It gives the column's value where the input carries it and the default where it does not, so a snippet works on data that may or may not have been given a @weight or a @v, and it never demands the column the way a bare @weight does.
@pscale = attr_or(1.0, "weight");
@P += attr_or({0.0, 0.0, 0.0}, "v") * $DT;
The default sets the type, and it has to match the attribute: a scalar default for a scalar column, a vector default for a vector one. A mismatch is a named compile error rather than a silent conversion.
Reading a column the input does not carry is a named error at cook time, listing the attribute and the columns that are available. It is never a silent zero. Match the attribute to the input type from the table above.