{"record":{"id":"2120c10fa0c228d9","repo":"BabylonJS/Babylon.js","slug":"extensioncontext-gaussian-splatting-primitive","errorCode":null,"errorMessage":"${extensionContext}: Gaussian splatting primitive is missing the POSITION attribute","messagePattern":"(.+?): Gaussian splatting primitive is missing the POSITION attribute","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/dev/loaders/src/glTF/2.0/Extensions/KHR_gaussian_splatting.pure.ts","lineNumber":91,"sourceCode":"        return GLTFLoader.LoadExtensionAsync<IKHRGaussianSplatting, AbstractMesh>(context, primitive, this.name, async (extensionContext) => {\r\n            if (primitive.mode != undefined && primitive.mode !== MeshPrimitiveMode.POINTS) {\r\n                throw new Error(`${extensionContext}: Gaussian splatting primitives must use POINTS mode`);\r\n            }\r\n\r\n            const loader = this._loader;\r\n\r\n            const loadAttribute = (attributeName: string): Nullable<Promise<Float32Array>> => {\r\n                const accessorIndex = primitive.attributes[attributeName];\r\n                if (accessorIndex == undefined) {\r\n                    return null;\r\n                }\r\n                const accessor = ArrayItem.Get(`${context}/attributes/${attributeName}`, loader.gltf.accessors, accessorIndex) as IAccessor;\r\n                return loader._loadFloatAccessorAsync(`/accessors/${accessor.index}`, accessor);\r\n            };\r\n\r\n            const positionsPromise = loadAttribute(\"POSITION\");\r\n            if (!positionsPromise) {\r\n                throw new Error(`${extensionContext}: Gaussian splatting primitive is missing the POSITION attribute`);\r\n            }\r\n\r\n            // Determine which spherical harmonics degrees are present (all lower degrees must exist per spec).\r\n            let shDegree = 0;\r\n            const shAttributeNames: string[] = [];\r\n            for (let degree = 1; degree <= 3; degree++) {\r\n                if (primitive.attributes[`KHR_gaussian_splatting:SH_DEGREE_${degree}_COEF_0`] == undefined) {\r\n                    break;\r\n                }\r\n                shDegree = degree;\r\n                for (let coef = 0; coef < ShCoefficientCountPerDegree[degree]; coef++) {\r\n                    shAttributeNames.push(`KHR_gaussian_splatting:SH_DEGREE_${degree}_COEF_${coef}`);\r\n                }\r\n            }\r\n\r\n            // Create the Gaussian Splatting mesh and assign it to the node synchronously (before awaiting the\r\n            // attribute data). The base loader wires the node's transform node from this assign call\r\n            // synchronously, so it must happen before the first await. The splat data is uploaded afterwards.\r","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/BabylonJS/Babylon.js/blob/0592b347b8a4ee0236089ea86a749cacfdb266d8/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_gaussian_splatting.pure.ts#L73-L109","documentation":"A KHR_gaussian_splatting primitive must supply a POSITION attribute (the splat center positions). The loader looks up primitive.attributes[\"POSITION\"] and throws when no accessor is bound for it, because the splat renderer cannot place points without positions.","triggerScenarios":"Loading a glTF asset whose gaussian-splatting primitive's 'attributes' object lacks a \"POSITION\" entry, or whose POSITION index points outside the accessors array so loadAttribute returns nothing.","commonSituations":"Hand-authored or minimally-generated glTF that lists splat SH/opacity attributes but forgot POSITION; an exporter bug dropping the POSITION accessor; stripping accessors during asset optimization and removing POSITION.","solutions":["Add a \"POSITION\" entry to the primitive's attributes pointing at a valid VEC3 float accessor","Re-export the asset from the source tool ensuring positions are written","Validate the asset with a glTF validator before loading"],"exampleFix":"// before\n\"attributes\": { \"scale_0\": 1, \"rotation_0\": 2 }\n// after\n\"attributes\": { \"POSITION\": 5, \"scale_0\": 1, \"rotation_0\": 2 }","handlingStrategy":"validation","validationCode":"function hasPosition(p) {\n  return typeof p.attributes?.POSITION === \"number\";\n}\n// check every gaussian-splatting primitive has attributes.POSITION before loading","typeGuard":"const hasPositionAttribute = (p: { attributes?: Record<string, number> }): p is { attributes: Record<string, number> & { POSITION: number } } => typeof p.attributes?.POSITION === \"number\";","tryCatchPattern":"try {\n  await loader.loadAsync(url);\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"missing the POSITION attribute\")) {\n    console.error(\"Splat primitive lacks POSITION; regenerate the asset.\");\n  } else throw e;\n}","preventionTips":["Never strip the POSITION accessor when optimizing splat assets","Validate glTF assets (attributes completeness) before shipping","Re-export assets after editing primitive attributes manually"],"tags":["gltf","loader","extension","gaussian-splatting","missing-attribute"],"backgroundTag":"gltf-required-attribute-missing","analyzedSha":"0592b347b8a4ee0236089ea86a749cacfdb266d8","analyzedAt":"2026-08-30T15:11:20.442Z","schemaVersion":2},"datasetVersion":"2026-08-30T18:17:15.746Z"}