Docs

Run modes: Texel and Corner

What one run corresponds to in the two per-element modes that are not Vertex or Triangle, and what each one may touch.

Part of the Wrangle reference. Everything on this page goes in the node's Code field, and runs once per element of the current Run Over mode.

Run over: Texel

Run Mode = Texel - Domain/Run Per is ignored.

What it is

Set Run Mode to Texel and the code runs once per texel of a durable output image this node owns, instead of once per vertex or triangle. The subject Mesh parameter becomes optional: undeclared or unwired, the code still runs (against whatever texture parameters it names); wired, it passes through unchanged and doubles as the "Mesh" donor slot.

Cd = texture("Base", uv.x, uv.y) * 0.5

Cd - vec3 rgb, read/write

The texel's own colour. Seeded from the first wired Texture parameter (Parameters-list order), sampled at this texel (matching size) or bilinear at the texel centre (mismatched size); black with alpha 1 when none is wired.

Cd = vec3(1, 1, 1) - Cd

Cd.a - scalar, read/write

The texel's own alpha.

Cd.a = 1

uv - scalar components, read only

uv.x/uv.y are this texel's own centre, normalised to [0,1] - the same coordinates you would hand texture()/texturea() to read this exact texel back. Assigning to uv here is a compile error: a texel's position is fixed by which one it is, the same reason a triangle's P cannot be assigned.

Cd = texture("Ramp", uv.x, 0.5)

elemnum / numelem - scalar, read

This texel's flat index / texel count, exactly like every other domain.

Cd.a = elemnum / numelem

texelx() / texely()

This texel's own coordinates, as unwrapped integers (0 .. width-1 / 0 .. height-1) - the same position uv.x/uv.y give normalised. Legal only in this domain.

vector c = texel("Albedo", texelx() + 1, texely())

Not available

Everything that describes a mesh element is a positioned compile error here: P, N, mask, @attributes, wmap(...) writes, area, ptnum, neighbourcount()/neighbour()/pointpos()/pointnormal()/isboundary() (the self-mesh, no-pin forms). A texel is not a vertex or a triangle and has none of those.

P, N, mask, @name, wmap(...) writes, area, ptnum - all errors

Donor-mesh functions (npoints/pointpos/pointattr/neardist/nearpoint/surfattr/sdist/raydist with a pin argument) and every texture function: their pins are independent of the run-over. Parameters (ch()/chf()/chv()/chi()/chb() and bare reads) too. Detail constants (bboxmin() and friends) also stay legal but need the subject Mesh parameter declared and wired - with none, calling one is a compile error naming it.

float t = pointattr("Mesh", "thickness", 0)

Output

The result is OutputTextureWidth x OutputTextureHeight texels (0 = auto: the first wired Texture parameter's own size, else 1024), published on this node's primary Texture output if one exists (see primary on the Parameters page) - a plain texture you can wire anywhere a texture pin is accepted. Switching Run Mode to Texel does not create that output; Create pins from code (or the pin editor) adds one when the expression writes Cd. A step-budget abort or a compile failure discards the whole run, exactly like the other two domains: nothing new publishes, and the persisted output from the last successful run is left untouched.

Run over: Corner

Run Mode = Corner - Domain/Run Per is ignored.

What it is

Set Run Mode to Corner and the code runs once per corner (face-vertex/wedge) instead of once per vertex or triangle: cornerid = 3*tid + c, c in 0..2, for every live triangle's three corners, ascending. A mesh whose MaxTriangleID() exceeds MAX_int32/3 refuses the run with a node error rather than overflow the id.

N = normalize(N)

Why a corner exists

An overlay element (N, uv, Cd) has exactly one parent vertex, and a triangle references three distinct vertices, so one triangle references a given overlay element at most once - a corner is that (triangle, element) pair. Reading/writing "this corner's N" therefore always means one exact overlay element, never a vertex's several split elements averaged together the way Vertex mode's N does.

Split, never merged

Writing N/uv/Cd per corner can make two corners that used to share an overlay element disagree - a hard edge or a UV seam being authored by the expression, not just read. When that happens the shared element splits: one corner keeps the original element, the other(s) get a freshly split one as a fresh split element, grouped by exact bitwise equality (see the next row for why exact). Corners already written to the same value, or left unwritten and therefore still equal to the old value, are never split from each other - only genuine disagreement splits. The reverse never happens: this run-over never calls MergeElement, even when two corners' new values happen to end up equal. A seam that was deliberately split (an artist's hard edge, a UV chart boundary) must stay split - silently welding it because today's write happened to agree would destroy information the mesh cannot get back, and unpredictably so, since it would depend on what the expression happened to compute this run.

if (materialid() == 2) { N = vec3(0,0,1) } // splits at the material boundary

Exact equality, not epsilon

Grouping corners that share a value uses bitwise equality, not a tolerance. An epsilon comparison is not transitive (a within eps of b, b within eps of c, but a not within eps of c is possible), so which corners end up grouped together would depend on the order they happen to be compared in - not deterministic. Two corners you intend to be "close enough" to share an element should be made exactly equal on purpose (round() or a shared computation, e.g. read one corner's N and assign it to the other) rather than relying on two similar expressions to land on the same float bits by chance.

N = round(N * 1000) / 1000 // quantise on purpose so nearby corners actually match

Determinism

Corner ids are visited in a fixed order (ascending cornerid, i.e. ascending triangle id then 0/1/2), and every step of the apply pass that decides which elements split - the touched-element set and the split grouping - runs in that same ascending order, single threaded. Running the same program on the same mesh twice always produces the same element count and the same values in the same order; only the parallel per-corner candidate computation (TryToFloat, blend, clamp - no element-count-affecting decision) runs across workers.

wmap reduction

Each named WeightMap output write (wmap("Name") = ...) is promoted to the per-vertex mean over corners that actually wrote it (a serial, ascending-corner-id sum/count per vertex, for float-sum determinism) - a vertex none of whose corners wrote it keeps 0, exactly like Vertex mode's own "never written" default. A single corner conditionally writing wmap("Weights") = 1 on a vertex with three corners gives that vertex 1.0, not 1/3 - the mean is over writers, not over every corner.

if (cornerindex == 0) { wmap("Weights") = 1 }

Per-vertex normal buffer

If the mesh has both the split normal overlay and the legacy per-vertex normal buffer, Corner mode writes only the overlay - the per-vertex buffer is left exactly as it was, with a run-summary Note that it was not updated (never silently discarded). Rebuild it downstream (Recompute Normals, or any node that reads the overlay) if you need it in sync.

Restrict

Mask tests the corner's owning vertex (every corner of a vertex shares its fate, so a vertex is never partly included). MaterialID / PolyGroup test the owning triangle. Same Restrict dropdown as Mesh Domain.

Restrict = Material ID, Restrict Value = 2

Not available

Everything that needs exactly one current vertex to write is a positioned compile error here: P write (use Run Per = Vertex), setboneweight/clearinfluences (skin writes - several corners can share a vertex, so which one "wins" has no answer), setpointpos (Detail only). @attribute writes are refused outright too (promotion policy deferred - see the Attributes section).

P = ..., setboneweight(...), @name = ... - all errors in Corner

Everything else a Vertex-mode program could do, reading through PtNum/PrimNum instead of a bare vertex/triangle id: isboundary (both forms), materialid()/polygroup() (owning triangle), wmap("Name") (owning vertex), geodist/relax/smooth_implicit (owning vertex), relbbox()/displace() (owning vertex/normal), the point-topology family (neighbourcount/neighbour/neighbourweight/pointpos/pointnormal) and prim-topology family (neighbourprimcount/neighbourprim), foreach neighbours/neighbourprims/points()/nearpoints, and skin reads (boneweight/weight/weighti/numinfluences/influencebone/influenceweight - the owning vertex's working copy). Donor-mesh/texture/landmark/ramp/transform/delta functions and Parameters, exactly like every other domain.

wmap("Weights") = weight("root") + wmap("Falloff")