export const computeGsplatCommonSource: "\n\n#include \"halfTypesCS\"\n\nconst TILE_SIZE: u32 = 16u;\n\nfn quatToMat3(r: half4) -> half3x3 {\n    let r2: half4 = r + r;\n    let x: half   = r2.x * r.w;\n    let y: half4  = r2.y * r;\n    let z: half4  = r2.z * r;\n    let w: half   = r2.w * r.w;\n\n    return half3x3(\n        half(1.0) - z.z - w,  y.z + x,              y.w - z.x,\n        y.z - x,              half(1.0) - y.y - w,   z.w + y.x,\n        y.w + z.x,            z.w - y.x,             half(1.0) - y.y - z.z\n    );\n}\n\nstruct SplatCov2D {\n    screen: vec2f,\n    a: f32,\n    b: f32,\n    c: f32,\n    viewDepth: f32,\n    valid: bool,\n    #if GSPLAT_AA\n        aaFactor: f32,\n    #endif\n}\n\nfn computeSplatCov(\n    worldCenter: vec3f,\n    rotation: half4,\n    scale: half3,\n    viewMatrix: mat4x4f,\n    viewProj: mat4x4f,\n    focal: f32,\n    viewportWidth: f32,\n    viewportHeight: f32,\n    nearClip: f32,\n    farClip: f32,\n    opacity: f32,\n    minPixelSize: f32,\n    isOrtho: u32,\n    alphaClip: f32,\n    minContribution: f32,\n    #ifdef GSPLAT_FISHEYE\n        fisheye_k: f32,\n        fisheye_inv_k: f32,\n        fisheye_projMat00: f32,\n        fisheye_projMat11: f32,\n    #endif\n) -> SplatCov2D {\n    var result: SplatCov2D;\n    result.valid = false;\n\n    let viewCenter = (viewMatrix * vec4f(worldCenter, 1.0)).xyz;\n\n    #ifdef GSPLAT_FISHEYE\n\n        // Generalized fisheye: g(\u03B8) = k\u00B7tan(\u03B8/k)\n        let fv = viewCenter;\n        let r_xy = length(fv.xy);\n        let neg_z = -fv.z;\n        let theta = atan2(r_xy, neg_z);\n\n        // Cull near the singularity at \u03B8 = k\u00B7\u03C0/2, and at camera origin\n        let maxTheta = min(fisheye_k * 1.5707963, 3.13);\n        if (theta > maxTheta - 0.01 || dot(fv, fv) < 0.0001) {\n            return result;\n        }\n\n        let tk = theta * fisheye_inv_k;\n        let sin_tk = sin(tk);\n        let cos_tk = cos(tk);\n        let g_theta = fisheye_k * sin_tk / cos_tk;\n        let fisheye_s = select(select(0.0, 1.0 / neg_z, neg_z > 0.0), g_theta / r_xy, r_xy > 1e-4);\n\n        let fndc = vec2f(fisheye_projMat00 * fisheye_s * fv.x, fisheye_projMat11 * fisheye_s * fv.y);\n        let screen = vec2f(\n            (fndc.x * 0.5 + 0.5) * viewportWidth,\n            (fndc.y * 0.5 + 0.5) * viewportHeight\n        );\n\n    #else\n\n        if (viewCenter.z > 0.0) {\n            return result;\n        }\n\n        let clip = viewProj * vec4f(worldCenter, 1.0);\n        let ndc = clip.xy / clip.w;\n        let screen = vec2f(\n            (ndc.x * 0.5 + 0.5) * viewportWidth,\n            (ndc.y * 0.5 + 0.5) * viewportHeight\n        );\n\n    #endif\n\n    let rot: half3x3 = quatToMat3(rotation);\n    let s: vec3f = vec3f(scale);\n    let M: mat3x3f = transpose(mat3x3f(\n        s.x * vec3f(rot[0]),\n        s.y * vec3f(rot[1]),\n        s.z * vec3f(rot[2])\n    ));\n\n    let w0 = vec3f(viewMatrix[0].x, viewMatrix[1].x, viewMatrix[2].x);\n    let w1 = vec3f(viewMatrix[0].y, viewMatrix[1].y, viewMatrix[2].y);\n    let w2 = vec3f(viewMatrix[0].z, viewMatrix[1].z, viewMatrix[2].z);\n\n    #ifdef GSPLAT_FISHEYE\n\n        // Fisheye Jacobian for g(\u03B8) = k\u00B7tan(\u03B8/k)\n        let fisheyeFocal = viewportWidth * fisheye_projMat00;\n        let g_prime = 1.0 / (cos_tk * cos_tk);\n        let d2 = dot(fv, fv);\n        let r_sq = max(r_xy * r_xy, 1e-8);\n        let K_coeff = select(0.0, (g_prime * neg_z / d2 - fisheye_s) / r_sq, r_xy > 1e-4);\n\n        let Jxx = fisheyeFocal * (fisheye_s + K_coeff * fv.x * fv.x);\n        let Jxy = fisheyeFocal * K_coeff * fv.x * fv.y;\n        let Jyy = fisheyeFocal * (fisheye_s + K_coeff * fv.y * fv.y);\n        let Jzx = fisheyeFocal * g_prime * fv.x / d2;\n        let Jzy = fisheyeFocal * g_prime * fv.y / d2;\n\n        let tt0 = Jxx * w0 + Jxy * w1 + Jzx * w2;\n        let tt1 = Jxy * w0 + Jyy * w1 + Jzy * w2;\n\n    #else\n\n        let ortho = isOrtho == 1u;\n        let v = select(viewCenter.xyz, vec3f(0.0, 0.0, 1.0), ortho);\n        let vz = select(min(v.z, -0.001), v.z, ortho);\n        let J1 = focal / vz;\n        let J2 = -J1 / vz * v.xy;\n\n        // Compute TT columns directly without materializing full J and W matrices.\n        // Original code:\n        //   let J = mat3x3f(vec3f(J1, 0.0, J2.x), vec3f(0.0, J1, J2.y), vec3f(0.0, 0.0, 0.0));\n        //   let W = transpose(mat3x3f(viewMatrix[0].xyz, viewMatrix[1].xyz, viewMatrix[2].xyz));\n        //   let TT = W * J;\n        let tt0 = J1 * w0 + J2.x * w2;\n        let tt1 = J1 * w1 + J2.y * w2;\n\n    #endif\n\n    // Fused covariance: cov = TT^T * Vrk * TT = TT^T * (M^T * M) * TT = (M * TT)^T * (M * TT).\n    // Compute B = M * TT then cov = B^T * B, avoiding the intermediate Vrk (mat3x3f) matrix.\n    let b0 = M * tt0;\n    let b1 = M * tt1;\n\n    let aRaw = dot(b0, b0);\n    let b = dot(b0, b1);\n    let cRaw = dot(b1, b1);\n\n    let a = aRaw + 0.3;\n    let c = cRaw + 0.3;\n\n    let det = a * c - b * b;\n    if (det <= 0.0) {\n        return result;\n    }\n\n    #if GSPLAT_AA\n        // AA compensation: ratio of pre-blur to post-blur determinant. Matches the\n        // quad renderer's GSPLAT_AA branch in gsplatCorner.js initCornerCov.\n        let detOrig = aRaw * cRaw - b * b;\n        result.aaFactor = sqrt(max(detOrig / det, 0.0));\n    #endif\n\n    // Rejects splats whose total visual contribution (opacity * projected area) is\n    // negligible. Near the camera, projected areas are large so contributions naturally\n    // exceed the threshold; at distance, areas shrink and low-impact splats are culled.\n    let totalContribution = opacity * 6.283185 * sqrt(det);\n    if (totalContribution < minContribution) {\n        return result;\n    }\n\n    // Opacity-aware radius tightening based on FlashGS\n    // https://github.com/InternLandMark/FlashGS\n    // The fixed factor 8.0 corresponds to power = -4.0 (exp(-4) \u2248 0.018).\n    // For low-opacity splats, pixels become invisible (alpha < alphaClip) at a closer\n    // distance. We solve for the power where opacity * exp(power) = alphaClip,\n    // giving radiusFactor = min(8.0, 2.0 * ln(opacity / alphaClip)). This shrinks\n    // the effective radius for low-opacity splats, reducing tile assignments.\n    let radiusFactor = computeRadiusFactor(half(opacity), alphaClip);\n\n    let vmin = min(1024.0, min(viewportWidth, viewportHeight));\n    let maxRadius = vmin;\n    let radiusXUncapped = sqrt(2.0 * a);\n    let radiusYUncapped = sqrt(2.0 * c);\n    let radiusX = min(radiusXUncapped, maxRadius);\n    let radiusY = min(radiusYUncapped, maxRadius);\n\n    if (max(radiusX, radiusY) < minPixelSize) {\n        return result;\n    }\n\n    // Frustum cull: reject splats entirely off-screen\n    if (screen.x + radiusX < 0.0 || screen.x - radiusX > viewportWidth ||\n        screen.y + radiusY < 0.0 || screen.y - radiusY > viewportHeight) {\n        return result;\n    }\n\n    // When the projected extent exceeds the radius cap, rescale the covariance\n    // so the Gaussian reaches its cutoff at the capped boundary. Without this,\n    // the Gaussian is still opaque at the boundary, creating hard rectangular\n    // edges. This matches the quad renderer's implicit UV renormalization.\n    let capScale = max(1.0, max(radiusXUncapped, radiusYUncapped) / maxRadius);\n    let invCapScale2 = 1.0 / (capScale * capScale);\n\n    result.screen = screen;\n    let scaledCov = vec3f(a, b, c) * invCapScale2;\n    result.a = scaledCov.x;\n    result.b = scaledCov.y;\n    result.c = scaledCov.z;\n    #ifdef GSPLAT_FISHEYE\n        result.viewDepth = sqrt(d2);\n    #else\n        result.viewDepth = -viewCenter.z;\n    #endif\n    result.valid = true;\n    return result;\n}\n";
