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.
Neighbours
Vertex or corner domain only; compile error in the triangle domain, except the prim forms which are triangle or corner.
| Syntax | What it does | Example |
|---|---|---|
neighbourcount(p) |
How many points share an edge with point p. neighborcount is the same function. |
wmap("Weights") = neighbourcount(ptnum) |
neighbour(p, i) |
The i-th one-ring neighbour of p, as a point id. neighbor is the same function. An out-of-range i returns p itself and is counted. |
wmap("Weights") = neighbour(ptnum, 0) |
neighbourweight(p, i) |
Cotangent weight of the i-th one-ring neighbour of p (uniform 1/valence fallback when the cotan contribution is degenerate). neighborweight is the same function. Same out-of-range convention as neighbour(). Vertex or Corner domain. |
float w = neighbourweight(ptnum, 0) |
pointpos(p) |
Position of any point, as a vector. In Vertex/Corner mode reads the mesh as it was before this expression ran. In Detail mode with setpointpos, reads the working copy so later foreach-points iterations see prior writes. | vector q = pointpos(neighbour(ptnum, 0)) |
pointnormal(p) |
Normal of any point, as a vector - the same split-element mean the N channel reads. | wmap("Weights") = dot(N, pointnormal(neighbour(ptnum, 0))) |
neighbourprimcount(t) |
How many triangles share an edge with triangle t. neighborprimcount is the same function. Triangle or Corner domain. |
wmap("Weights") = neighbourprimcount(primnum) |
neighbourprim(t, i) |
The i-th edge-adjacent triangle id. neighborprim is the same function. Out-of-range returns t itself and is counted. Triangle or Corner domain. |
float n = neighbourprim(primnum, 0) |
smooth_implicit(lambda) |
One whole-mesh implicit smooth (M + lambda L) X = M P0 solved directly, as a pre-pass. lambda is a compile-time positive scalar. Returns the smoothed position of the current vertex (the owning vertex, in Corner). Vertex or Corner only. | P = smooth_implicit(1) |
smoothing (recipe) |
One-ring Laplacian smooth, the reason loops and neighbours exist. Prefer relax(n) or smooth_implicit(lambda) for a pre-pass; or sum neighbourweight() by hand. | vector s = vec3(0,0,0); float n = 0; foreach (nb in neighbours(ptnum)) { s += pointpos(nb); n += 1 }; if (n > 0) { P = lerp(P, s / n, 0.5) } |
isboundary() or isboundary(p)
1 if the point lies on the mesh boundary (any of its edges is shared by only one triangle), else 0. Isolated vertices with no edges are 0. Out-of-range p returns 0 and is counted. isboundary() is Vertex or Corner (current vertex; in Corner, the corner's owning vertex). isboundary(p) is Vertex, Corner or Detail.
if (isboundary()) { Cd = vec3(1,0,0) }
relax(n)
Jacobi cotangent-weighted smooth, run as a whole-mesh pre-pass before the per-vertex expression. n is a compile-time positive integer literal, at most 4096; asking for more is a compile error naming the ceiling, not a silently smaller n. Returns the relaxed position of the current vertex (in Corner, the corner's owning vertex). Vertex or Corner only.
P = relax(4)
Transforms
| Syntax | What it does | Example |
|---|---|---|
rotatevector(v, axis, degrees) |
Rodrigues rotation of v about axis by degrees. Axis need not be unit; a zero axis leaves v unchanged (counted). | P = rotatevector(P, vec3(0,0,1), 90) |
dihedral(v, a, b) |
Rotate v by the rotation that takes a onto b. | P = dihedral(P, vec3(0,0,1), N) |
polar(v) |
Cylindrical: vec3(r, theta, z) with r = hypot(x,y), theta = atan2(y,x). | vector c = polar(P) |
frompolar(r, theta, z) |
Inverse of polar: vec3(rcos(theta), rsin(theta), z). | P = frompolar(c.x, c.y, c.z) |
relbbox() or relbbox(p) |
(p - bboxmin()) / bboxsize(), with a zero size component guarded to 0. Zero-arg form uses P (Vertex/Triangle/Corner). Explicit p works in Detail too. | wmap("Weights") = relbbox().z |
displace(s) / displace(s, mode) |
normalize(N) * s, area-weighted by default (vertex area / mean area). mode 0 = unweighted, nonzero = area (default). Does not write P. Vertex or Corner only (area weight uses the owning vertex, in Corner). | P += displace(2) |
ident / ident3 ident() / ident3() |
Identity matrix4 / matrix3. | matrix M = ident() |
quaternion(axis, degrees) |
Quaternion from axis-angle. | quaternion q = quaternion(vec3(0,0,1), 90) |
quaternion / quat quaternion(m3) / quat(m3) |
Quaternion from a rotation matrix3 (Shepperd's method), renormalised. Assumes m3 is a pure rotation - feed it a scale/shear matrix and you get an answer, not an error. | quaternion q = quaternion(lookat(P, target, vec3(0,0,1))) |
slerp(q1, q2, t) |
Spherical linear interpolation. Takes the short arc (a quaternion and its negative represent the same rotation, and slerp knows it), falls back to a plain lerp when the two are nearly identical, and always returns a unit quaternion. | quaternion q = slerp(qa, qb, 0.5) |
eulertoquat / quattoeuler eulertoquat(v) / quattoeuler(q) |
Convert between a quaternion and XYZ-degree Euler angles - the same convention maketransform's euler argument uses, so the two always agree. quattoeuler clamps at the +-90 degree gimbal singularity instead of returning NaN. | quaternion q = eulertoquat(vec3(0, 90, 0)) |
qmul / qrotate qmul(a,b) / qrotate(q,v) |
Quaternion product; rotate a vector by a quaternion. | vector r = qrotate(q, vec3(1,0,0)) |
lookat(from, to, up) |
matrix3 basis: Z=normalize(to-from), X=normalize(cross(up,Z)), Y=cross(Z,X). Degenerate -> ident3 (counted). | matrix3 R = lookat(P, target, vec3(0,0,1)) |
maketransform(t, euler_deg, s) / maketransform(t, q, s) |
matrix4 TRS. Euler is XYZ degrees. | matrix M = maketransform(t, r, s) |
ptransform / vtransform / ntransform (p|v|n, m4|m3) |
ptransform applies Rp+t (m4) or Rp (m3); vtransform is R*v; ntransform is inverse-transpose then normalize. | P = ptransform(P, M) |
invert / transpose invert(m3|m4|q) / transpose(m3|m4) |
Same-type inverse/transpose. Singular -> identity (counted). | matrix Minv = invert(M) |
Geodesic distance
Vertex or Corner domain only; compile error in the triangle domain, Texel or a Custom Force expression.
geodist("SeedMap")
Geodesic (along-the-surface, heat-method) distance from the current vertex (the owning vertex, in Corner) to the nearest painted vertex of the named Weight Map parameter pin - a whole-mesh pre-pass, computed once per distinct seed map before the run starts, exactly like relax()/smooth_implicit(). "Painted" means weight above the same small epsilon Restrict-by-Mask uses. An empty/missing seed map, or a solver failure on a disconnected/degenerate mesh, leaves every distance at a large finite sentinel (1e19) - far enough that a falloff built from it reads as zero influence, still a normal number the rest of the language can do arithmetic on.
wmap("Weights") = 1 - saturate(geodist("Seeds") / 50)