export const computeGsplatIntervalCullSource: "\n\nstruct Interval {\n    workBufferBase: u32,\n    splatCount: u32,\n    boundsIndex: u32,\n    pad: u32\n};\n\nstruct BoundsEntry {\n    centerX: f32,\n    centerY: f32,\n    centerZ: f32,\n    radius: f32,\n    transformIndex: u32,\n    pad0: u32,\n    pad1: u32,\n    pad2: u32\n};\n\nstruct CullUniforms {\n#ifdef GSPLAT_FISHEYE\n    cameraWorldPos: vec3f,\n    maxTheta: f32,\n    cameraForward: vec3f,\n    numIntervals: u32,\n#else\n    frustumPlanes: array<vec4f, 6>,\n    numIntervals: u32\n#endif\n};\n\n@group(0) @binding(0) var<uniform> uniforms: CullUniforms;\n\n@group(0) @binding(1) var<storage, read> intervals: array<Interval>;\n\n@group(0) @binding(2) var<storage, read_write> countBuffer: array<u32>;\n\n@group(0) @binding(3) var<storage, read> boundsBuffer: array<BoundsEntry>;\n@group(0) @binding(4) var<storage, read> transformsBuffer: array<vec4f>;\n\n@compute @workgroup_size({WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n    let idx = gid.x;\n    if (idx < uniforms.numIntervals) {\n        let interval = intervals[idx];\n\n        let entry = boundsBuffer[interval.boundsIndex];\n        let localCenter = vec3f(entry.centerX, entry.centerY, entry.centerZ);\n        let radius = entry.radius;\n\n        let base = entry.transformIndex * 3u;\n        let row0 = transformsBuffer[base];\n        let row1 = transformsBuffer[base + 1u];\n        let row2 = transformsBuffer[base + 2u];\n\n        let worldCenter = vec3f(\n            dot(vec4f(localCenter, 1.0), row0),\n            dot(vec4f(localCenter, 1.0), row1),\n            dot(vec4f(localCenter, 1.0), row2)\n        );\n\n        let worldRadius = radius * length(vec3f(row0.x, row1.x, row2.x));\n\n    #ifdef GSPLAT_FISHEYE\n        let v = worldCenter - uniforms.cameraWorldPos;\n        let d = length(v);\n        var visible = true;\n        if (d > worldRadius) {\n            let cosAngle = dot(v / d, uniforms.cameraForward);\n            let theta = acos(clamp(cosAngle, -1.0, 1.0));\n            let angularRadius = asin(min(worldRadius / d, 1.0));\n            visible = (theta - angularRadius) < uniforms.maxTheta;\n        }\n    #else\n        var visible = true;\n        for (var p = 0; p < 6; p++) {\n            let plane = uniforms.frustumPlanes[p];\n            let dist = dot(plane.xyz, worldCenter) + plane.w;\n            if (dist <= -worldRadius) {\n                visible = false;\n                break;\n            }\n        }\n    #endif\n\n        countBuffer[idx] = select(0u, interval.splatCount, visible);\n    }\n\n    // Thread 0 writes sentinel so prefixSum[numIntervals] = total visible count\n    if (idx == 0u) {\n        countBuffer[uniforms.numIntervals] = 0u;\n    }\n}\n";
export default computeGsplatIntervalCullSource;
