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

    Variable MESH_PICK_VERTEX_SHADERConst

    MESH_PICK_VERTEX_SHADER: "\n precision highp float;\n\n \nbool isInvalidFloat(float v) {\n return isnan(v) || isinf(v);\n}\n\nfloat sanitizePositive(float v, float fallback) {\n return (isInvalidFloat(v) || v <= 0.0) ? fallback : v;\n}\n\nfloat sanitizeNonNegative(float v, float fallback) {\n return (isInvalidFloat(v) || v < 0.0) ? fallback : v;\n}\n\n// Per-element opacity sanitizer: NaN/Inf route to the 1.0 opaque\n// identity (corruption stays LOUD), finite values clamp to [0, 1]\n// (alpha is opacity, never HDR — Python pins the range at write; this\n// guards hand-crafted zarr). The clamp keeps the zero boundary\n// CONTINUOUS (a -1e-4 epsilon vanishes like +0.0 renders, instead of\n// jumping to full opacity) and keeps the value mediump-varying-safe.\nfloat sanitizeAlpha(float v) {\n return isInvalidFloat(v) ? 1.0 : clamp(v, 0.0, 1.0);\n}\n\n \n// Element index split into two 16-bit halves, low in .x and high in .y.\n// The pick pass carries the index through an RGBA32F buffer, and float32\n// has a 24-bit mantissa — so a single float channel cannot represent\n// consecutive indices past 16,777,216, while a node's capacity reaches\n// 2^25 on a 32768-texel device. Both halves are <= 65535, hence exact,\n// and the pick decoder recombines them (see picking-system/pick-render.ts).\n// Kept in INT space: doing the split on a float would already have lost\n// the bit it is meant to preserve.\nvec2 luxarElementIdSplit(uint i) {\n return vec2(float(i & 0xFFFFu), float(i >> 16u));\n}\n\n\n // Per-vertex colour; only .a is read here (the pick pass has no colour output).\n // Always bound — see createMeshDefaultColorAttribute. RGB input gives w = 1.0.\n in vec4 color;\n\n uniform float uNodeId;\n\n flat out highp float vNodeId;\n flat out highp vec2 vElementId;\n out mediump float vAlpha;\n #ifdef LUXAR_MESH_PICK_BASE_COLOR_TEX\n // `uv` is three's own auto-declared attribute, like `position` above.\n out mediump vec2 vUv;\n #endif\n // VIEW-space depth for the fragment stage's near fade. highp: it is compared\n // against a scene-relative uNearCull that can be ~1e-3 of the scene diagonal,\n // which mediump cannot resolve on a large scene.\n out highp float vViewZ;\n\n void main() {\n vNodeId = uNodeId;\n // Vertex ordinal, NOT a triangle ordinal and NOT a storage slot: mesh has\n // no ordering attribute to indirect through, and gl_VertexID under an\n // indexed draw is already the stable per-vertex id (§6.5).\n vElementId = luxarElementIdSplit(uint(gl_VertexID));\n // Sanitized identically to the visual shader: alpha is the whole coverage\n // term for a mesh, and a NaN would survive into the cutout comparison as a\n // fragment that never discards — pickable where the visual has a hole.\n vAlpha = sanitizeAlpha(color.a);\n #ifdef LUXAR_MESH_PICK_BASE_COLOR_TEX\n vUv = uv;\n #endif\n vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);\n vViewZ = mvPosition.z;\n gl_Position = projectionMatrix * mvPosition;\n }\n " = ...

    Picking vertex stage for meshes.

    position is three's own auto-declared attribute; color is ours. It is the ONLY attribute this stage needs beyond position — the element id comes from the gl_VertexID built-in, and normal/aScalar are shading inputs with no bearing on which vertex was clicked.

    color is read as a vec4 for the same reason the visual shader does: GL fills a size-3 attribute's missing components with (0, 0, 0, 1), so RGB data supplies the opaque w = 1.0 for free.

    The two id varyings are flat — see the provoking-vertex note on the fragment stage. vAlpha is smoothly interpolated, exactly as in the visual shader, so the cutout hole in the pick pass has the same shape as the one on screen.

    vViewZ carries the view-space depth the fragment stage's near fade needs. Just the z, not the whole vec3 the visual pair carries — the visual stage differentiates its vViewPos for the flat-normal fallback, and the pick pass has no shading to do.