Variable POINT_VERTEX_SHADERConst
POINT_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 \nfloat perspectiveNearFade(int isOrtho, float viewZ, float nearCull) {\n if (isOrtho == 1) return 1.0;\n if (viewZ >= 0.0) return 0.0;\n return smoothstep(nearCull, nearCull * 2.0, -viewZ);\n}\n\n \nint luxarIsOrthoProjection() {\n return projectionMatrix[3][3] > 0.5 ? 1 : 0;\n}\nfloat luxarProjectionSizeScale() {\n return abs(projectionMatrix[1][1]);\n}\n\n\n // Per-vertex (4 corners): -1..1 normalised quad coordinates.\n in vec2 aQuadCorner;\n\n // Draw-slot → storage-slot mapping, double-buffered so a new ordering\n // swaps atomically (declaration + luxarSortedIndex() in glsl-lib).\n // Uint32Array attributes → bound via vertexAttribIPointer, matching\n // the uint declarations.\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\nin uint aSortedIndex;\nin uint aSortedIndexB;\nuniform int uSortedIndexSlot;\n\nuint luxarSortedIndex() {\n return uSortedIndexSlot == 1 ? aSortedIndexB : aSortedIndex;\n}\n\n// The STORAGE slot's id parts — the id the rest of the pipeline (loaders,\n// selection) addresses elements by, not the transient draw slot. Mesh reads\n// the shared split directly instead, off gl_VertexID (spec §6.5).\nvec2 luxarElementIdParts() {\n return luxarElementIdSplit(luxarSortedIndex());\n}\n// Projected-density thinning (scene/density-guard.ts): the fraction of this\n// node's elements to DROP, chosen per STORAGE index with a deterministic\n// integer hash so the kept subset is stable under depth re-sorting, identical\n// across the visual and picking passes, and spatially uniform (storage order\n// is Hilbert/BSP-coherent, so a prefix would be a hole). A material that does\n// not set the uniform reads 0 and drops nothing.\nuniform float uDensityDrop;\nbool luxarDensityDropped() {\n if (uDensityDrop <= 0.0) return false;\n uint h = luxarSortedIndex();\n h ^= h >> 16u;\n h *= 0x7feb352du;\n h ^= h >> 15u;\n h *= 0x846ca68bu;\n h ^= h >> 16u;\n return float(h) * (1.0 / 4294967296.0) < uDensityDrop;\n}\n\n\n // Point data texture: RGBA32F, 3 texels/point (see\n // rendering/element-texture-layout.ts for the texel layout).\n uniform highp sampler2D uPointTex;\n\n #ifdef USE_COLORMAP\n uniform sampler2D uColormapTex; // 256x1 LUT texture\n uniform float uScalarMin; // Scalar range minimum (display-range window)\n uniform float uScalarScale; // 1.0 / (max - min)\n uniform mediump float uInvGamma; // Gamma applied to the VALUE, pre-LUT (see note below)\n #endif\n\n uniform float maxPointSize; // Pre-computed: resolution.y * 0.5\n uniform float radiusScale;\n uniform vec2 uResolution; // Physical framebuffer size in pixels\n uniform float uPixelRatio; // Physical framebuffer pixels per CSS pixel\n uniform float uNearCull; // Near-fade start distance (world units)\n\n out mediump vec3 vColor;\n out mediump float vBeta; // Super-Gaussian exponent beta (per-instance)\n out highp float vRadius; // Pass radius to fragment for zero-check (needs precision)\n out mediump vec2 vSpriteCoord; // [0, 1] sprite UV, replaces gl_PointCoord\n out mediump float vPointSize; // RAW projected size (pre-clamp) for sub-pixel compensation\n out mediump float vNearFade; // Perspective near fade (1.0 under ortho)\n flat out mediump float vAlpha; // per-point opacity (texel2.y; 1.0 for RGB data)\n\n void main() {\n // === Point-texture fetch prologue ===\n // texelFetch reads reconstruct the per-point values into the exact\n // local names the math below has always used — zero changes\n // downstream of this block. The width is a multiple of 3\n // (element-texture-layout.ts), so a point's 3 texels share one row\n // and only x advances. texel2 carries the colormap scalar (.x,\n // read under USE_COLORMAP) and the per-point alpha (.y, written\n // unconditionally by the texel writer — 1.0 for RGB data).\n // Projected-density thinning (density-guard): drop this instance before\n // any texel fetch; the rasterizer discards a z=-2 vertex.\n if (luxarDensityDropped()) {\n gl_Position = vec4(0.0, 0.0, -2.0, 1.0);\n return;\n }\n int pointBase = int(luxarSortedIndex()) * 3;\n int pointTexW = LUXAR_POINT_TEX_W;\n ivec2 texel0 = ivec2(pointBase % pointTexW, pointBase / pointTexW);\n vec4 pointT0 = texelFetch(uPointTex, texel0, 0);\n vec4 pointT1 = texelFetch(uPointTex, ivec2(texel0.x + 1, texel0.y), 0);\n vec4 pointT2 = texelFetch(uPointTex, ivec2(texel0.x + 2, texel0.y), 0);\n vec3 aCenter = pointT0.xyz; // world-space centre\n float aRadius = pointT0.w;\n vec3 aColor = pointT1.rgb;\n float aSharpness = pointT1.w;\n // Per-point opacity (1.0 for RGB data). Sanitized: alpha is\n // load-bearing in EVERY mode (linear contribution scale) and maps\n // into optical depth under volumetric, where a NaN/Inf poisons\n // τ past the discard into NaN pixels — and a huge finite value\n // would blow out the linear folds (or overflow the mediump\n // varying). Python validation pins alpha to [0, 1] at write; this\n // guards hand-crafted zarr. NaN/Inf → the 1.0 opaque identity\n // (loud); finite values clamp to [0, 1] (a negative epsilon\n // vanishes continuously instead of flipping opaque). The gsplat\n // twin does the same.\n vAlpha = sanitizeAlpha(pointT2.y);\n #ifdef USE_COLORMAP\n float aScalar = pointT2.x; // per-point scalar for colormap lookup\n #endif\n\n // Pass vertex color — either from attribute or colormap LUT.\n // In colormap mode the display range (uScalarMin/uScalarScale) and\n // gamma shape the scalar VALUE before the LUT lookup, not the\n // resulting color. Intensity/offset apply POST-LUT to the mapped\n // color (fragment shader, matching the gsplat shader) so the layer\n // gain/offset controls work on colormapped nodes too.\n #ifdef USE_COLORMAP\n float t = clamp((aScalar - uScalarMin) * uScalarScale, 0.0, 1.0);\n #ifndef LUXAR_GAMMA_ONE\n t = pow(t, uInvGamma); // gamma on the value, pre-LUT\n #endif\n vColor = texture(uColormapTex, vec2(t, 0.5)).rgb;\n #else\n vColor = aColor;\n #endif\n\n // Sharpness is authored in [0, 1] and maps to the super-Gaussian\n // exponent beta = 2^(6s - 2): s=0.5 -> beta=2 (a true Gaussian, the\n // gsplat member), higher s -> harder edge, lower s -> peakier cusp.\n // sanitizeNonNegative keeps a valid s=0 (-> beta=0.25) and routes\n // NaN/Inf/negative to the 0.5 default; clamp guards the [0, 1] range.\n float s = clamp(sanitizeNonNegative(aSharpness, 0.5), 0.0, 1.0);\n vBeta = exp2(6.0 * s - 2.0);\n\n // Apply radius scale for dtype normalization (e.g., uint8 needs 1/255 scale)\n float normalizedRadius = sanitizeNonNegative(aRadius * radiusScale, 0.0);\n vRadius = normalizedRadius; // Pass to fragment shader\n\n // Transform per-instance centre from world space to view + clip space.\n vec4 mvPosition = modelViewMatrix * vec4(aCenter, 1.0);\n\n // Unified near handling (matches line + gsplat shaders): behind-\n // camera vertices fade to 0 (the quad expansion multiplies by\n // projCenter.w, which is <= 0 there and would flip the sprite),\n // near-plane approach fades smoothly across [nearCull, 2*nearCull]\n // instead of drawing a full-brightness maxPointSize sprite until\n // z crosses 0. Ortho: fade = 1, NDC clipping is the authority.\n // uNearCull is scene-bounds-scaled (diagonal * 0.001, see\n // scene-bounds-cache.ts) so the fade band tracks the scene scale;\n // the 1e-20 floor only guards the degenerate smoothstep\n // (edge0 == edge1) when uNearCull is exactly 0. An absolute 1e-4\n // floor here overrode the scene-relative value on tiny-unit\n // scenes (diagonal ~1e-6 put the WHOLE scene inside the fade\n // band and every vertex was rejected).\n vNearFade = perspectiveNearFade(luxarIsOrthoProjection(), mvPosition.z, max(uNearCull, 1e-20));\n if (vNearFade < 0.01) {\n gl_Position = vec4(0.0, 0.0, -2.0, 1.0); // off-screen → no fragments\n return;\n }\n\n vec4 projCenter = projectionMatrix * mvPosition;\n\n // World-space point sizing from VIEW-SPACE DEPTH (-mvPosition.z),\n // matching the line + gsplat shaders: screen-space size scales\n // with view-z, not Euclidean distance from the camera position,\n // so identical points render the same size across the field of\n // view (Euclidean shrank edge-of-screen points by cos(theta)).\n // The 1e-20 floor is a pure divide-by-zero guard, NOT a scale\n // floor: the near-fade reject above already guarantees surviving\n // vertices have -z ≳ uNearCull (scene-relative), and the\n // clamp(basePointSize, 1.5 * uPixelRatio, maxPointSize) below bounds the\n // output either way. The old absolute 1e-4 clamped VALID depths\n // on tiny-unit scenes (-z ~ 1e-6), shrinking every sprite ~100×.\n float invDistance = (luxarIsOrthoProjection() == 1) ? 1.0 : 1.0 / max(-mvPosition.z, 1e-20);\n float sizeFactor = 2.0 * uResolution.y * luxarProjectionSizeScale();\n float basePointSize = normalizedRadius * sizeFactor * invDistance;\n\n // The shifted-truncated super-Gaussian falloff (fragment shader)\n // truncates to zero exactly at the sprite edge (rho = 1), so the\n // sprite size already IS the visible extent — no sharpness-dependent\n // size compensation is needed (the old polynomial kernel required it).\n //\n // Minimum sprite size 1.5px, matching the LINE shader: quads\n // thinner than ~1.5px cause rasterization gaps (flicker).\n // Below 1× render scale keep the historical 1.5 framebuffer-pixel\n // floor rather than shrinking below one sample.\n // Sub-pixel points keep their visual weight via the fragment's\n // sizeScale^2 energy compensation (vPointSize carries the raw,\n // pre-clamp size). Zero-radius filtering happens in the fragment.\n vPointSize = basePointSize;\n float minPointSize = 1.5 * max(uPixelRatio, 1.0);\n float pointSize = clamp(basePointSize, minPointSize, maxPointSize);\n\n // Expand the unit quad to a screen-space sprite. aQuadCorner is\n // in [-1, 1] per axis, so aQuadCorner * (pointSize / uResolution)\n // is the half-extent in NDC space. Multiply by projCenter.w to\n // convert NDC delta to clip-space delta (compensating for the\n // upcoming perspective divide).\n vec2 offsetClip = aQuadCorner * (pointSize / uResolution) * projCenter.w;\n gl_Position = projCenter + vec4(offsetClip, 0.0, 0.0);\n\n // Sprite UV in [0, 1]² — fragment shader uses this in place of\n // gl_PointCoord (which is unavailable under THREE.Mesh).\n vSpriteCoord = (aQuadCorner + 1.0) * 0.5;\n }\n " = ...