The displayed axis triple, in x/y/z order.
normal_dims when the node has stored normals.
The node's authored double_sided.
Whether this epoch's shader reads the stored
normal attribute, i.e. whether anything consumes gl_FrontFacing. Pass
storedNormalsUsable's result. Keyed on the normal-dims match alone
rather than also on shading === 'smooth': a needless flip on a
flat-shaded double-sided node is invisible — coverage is identical and the
derivative normal is orientation-defined by the fragment — so buying
correctness here costs no extra plumbing.
Decide winding for one
displayDimsepoch.Why this is not simply "flip if the permutation is odd"
Winding is only decidable against the authored winding frame — the axis triple the stored face order is front-facing in, which is
sorted(normal_dims)(§3.2). Three cases follow, and they are genuinely different:double_sided: falsemesh renders inside-out, which for an open surface means it vanishes entirely.[0,1,2]→[1,2,3]), or the mesh declares no frame at all (no stored normals). Projected orientation is then per-triangle data-dependent — some triangles project front-facing, some back — and no index post-pass can fix it. The epoch renders double-sided regardless ofdouble_sided: false.Case 2's reversal is keyed to the current
displayDimsparity, not to the event ofdisplayDimschanging, so it must run on every index build in an odd-parity epoch: initial load, slice move, anddisplayDimschange alike. Nothing restricts the opening view to ascending order, so the very first build can already need it.Why a double-sided mesh is NOT exempt
This function used to return early for
doubleSided, reasoning that "both orientations draw, so parity is unobservable". That is true of rasterization COVERAGE and false ofgl_FrontFacing, which the stored-normal fragment variant reads to flip the interpolated normal toward the camera (materials/mesh/shader-glsl.ts, and the TSL twin via thefrontFacingnode). In an odd-parity epoch every projected triangle's winding is reversed, so that flip lands on the wrong side: the wrap term drops to the dim end of its range and the surface shades with an inverted gradient collapsing towarduAmbient— precisely the artifact the flip exists to remove.Spec §5.4 says exactly this, in the sentence that rules out the alternative
side-swap implementation: "the stored-normal shading flip (§6.2) requires the index post-pass form, since the opposite side of aDoubleSidemesh isDoubleSideand leaves projected winding (and thusgl_FrontFacing) reversed." Anddouble_sideddefaults true (§3.3), so the exemption covered the DEFAULT configuration.So
reverseis decided whenever winding is observable at all — when the material culls, OR when the stored-normal variant is active.sidestill comes fromdoubleSidedalone, and the undecidable-frame notice stays gated on it too: a mesh that already draws both sides has nothing to be told about aDoubleSidefallback.