Luxar Viewer API Documentation - v2026.9.22
    Preparing search index...

    Function resolveWinding

    • Decide winding for one displayDims epoch.

      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:

      1. The displayed triple equals the frame, even permutation. Draw as authored.
      2. The displayed triple equals the frame, odd permutation. Display space is a reflection of the frame, so every projected triangle's orientation is reversed — uniformly. Swapping two of each triangle's three indices restores it. Without this a double_sided: false mesh renders inside-out, which for an open surface means it vanishes entirely.
      3. The displayed triple is a DIFFERENT triple than the frame (e.g. [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 of double_sided: false.

      Case 2's reversal is keyed to the current displayDims parity, not to the event of displayDims changing, so it must run on every index build in an odd-parity epoch: initial load, slice move, and displayDims change alike. Nothing restricts the opening view to ascending order, so the very first build can already need it.

      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 of gl_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 the frontFacing node). 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 toward uAmbient — 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 a DoubleSide mesh is DoubleSide and leaves projected winding (and thus gl_FrontFacing) reversed." And double_sided defaults true (§3.3), so the exemption covered the DEFAULT configuration.

      So reverse is decided whenever winding is observable at all — when the material culls, OR when the stored-normal variant is active. side still comes from doubleSided alone, and the undecidable-frame notice stays gated on it too: a mesh that already draws both sides has nothing to be told about a DoubleSide fallback.

      Parameters

      • displayDims: readonly number[]

        The displayed axis triple, in x/y/z order.

      • normalDims: readonly number[] | undefined

        normal_dims when the node has stored normals.

      • doubleSided: boolean

        The node's authored double_sided.

      • storedNormalsActive: boolean

        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.

      Returns WindingDecision