The glTF File Format.
The complete code is given below for the loader/dumper. You can try an online version here (LINK ).
The 100 lines of code below is a glTF parser written in vanilla JavaScript for loading, parsing and dumping the data for the 3d model in the file.
class glTFLoader { async load ( url , gltfPath ) { try { const response = await fetch ( url ); const gltf = await response . json (); gltf . gltfPath = gltfPath ; const parsedData = await this . parseGLTF ( gltf ); return parsedData ; } catch ( error ) { console . error ( 'Error loading GLTF file:' , error ); throw error ; } } async parseGLTF ( gltf ) { const parsedData = { buffers : await this . loadBuffers ( gltf ), bufferViews : gltf . bufferViews , accessors : gltf . accessors , meshes : gltf . meshes , nodes : gltf . nodes , materials : gltf . materials , textures : gltf . textures , animations : gltf . animations , }; this . extractMeshData ( parsedData ); this . extractAnimations ( parsedData ); this . extractTextures ( parsedData ); return parsedData ; } async loadBuffers ( gltf ) { const bufferPromises = gltf . buffers . map ( buffer => fetch ( gltf . gltfPath + buffer . uri ). then ( res => res . arrayBuffer ())); return await Promise . all ( bufferPromises ); } extractMeshData ( parsedData ) { parsedData . meshes .forEach( mesh => { mesh . primitives .forEach( primitive => { const positionAccessorIndex = primitive . attributes . POSITION ; const positionAccessor = parsedData . accessors [ positionAccessorIndex ]; const positionBufferView = parsedData . bufferViews [ positionAccessor . bufferView ]; const positionBuffer = parsedData . buffers [ positionBufferView . buffer ]; const positions = new Float32Array ( positionBuffer , positionBufferView . byteOffset + ( positionAccessor . byteOffset || 0 ), positionAccessor . count * ( positionAccessor . type === 'VEC3' ? 3 : 1 ) ); primitive . positions = positions ; if ( primitive . indices !== undefined ) { const indexAccessor = parsedData . accessors [ primitive . indices ]; const indexBufferView = parsedData . bufferViews [ indexAccessor . bufferView ]; const indexBuffer = parsedData . buffers [ indexBufferView . buffer ]; const indices = new Uint16Array ( indexBuffer , indexBufferView . byteOffset + ( indexAccessor . byteOffset || 0 ), indexAccessor . count ); primitive . indices = indices ; } if ( primitive . attributes . NORMAL !== undefined ) { const normalAccessorIndex = primitive . attributes . NORMAL ; const normalAccessor = parsedData . accessors [ normalAccessorIndex ]; const normalBufferView = parsedData . bufferViews [ normalAccessor . bufferView ]; const normalBuffer = parsedData . buffers [ normalBufferView . buffer ]; const normals = new Float32Array ( normalBuffer , normalBufferView . byteOffset + ( normalAccessor . byteOffset || 0 ), normalAccessor . count * ( normalAccessor . type === 'VEC3' ? 3 : 1 ) ); primitive . normals = normals ; } if ( primitive . attributes . TEXCOORD_0 !== undefined ) { const texCoordAccessorIndex = primitive . attributes . TEXCOORD_0 ; const texCoordAccessor = parsedData . accessors [ texCoordAccessorIndex ]; const texCoordBufferView = parsedData . bufferViews [ texCoordAccessor . bufferView ]; const texCoordBuffer = parsedData . buffers [ texCoordBufferView . buffer ]; const texCoords = new Float32Array ( texCoordBuffer , texCoordBufferView . byteOffset + ( texCoordAccessor . byteOffset || 0 ), texCoordAccessor . count * ( texCoordAccessor . type === 'VEC2' ? 2 : 1 ) ); primitive . texCoords = texCoords ; } }); }); } extractAnimations ( parsedData ) { if ( parsedData . animations ) { parsedData . animations .forEach( animation => { animation . samplers .forEach( sampler => { const inputAccessor = parsedData . accessors [ sampler . input ]; const inputBufferView = parsedData . bufferViews [ inputAccessor . bufferView ]; const inputBuffer = parsedData . buffers [ inputBufferView . buffer ]; sampler . inputData = new Float32Array ( inputBuffer , inputBufferView . byteOffset + ( inputAccessor . byteOffset || 0 ), inputAccessor . count ); const outputAccessor = parsedData . accessors [ sampler . output ]; const outputBufferView = parsedData . bufferViews [ outputAccessor . bufferView ]; const outputBuffer = parsedData . buffers [ outputBufferView . buffer ]; sampler . outputData = new Float32Array ( outputBuffer , outputBufferView . byteOffset + ( outputAccessor . byteOffset || 0 ), outputAccessor . count * ( outputAccessor . type === 'VEC3' ? 3 : 1 ) ); }); }); } } extractTextures ( parsedData ) { if ( parsedData . textures ) { parsedData . textures .forEach( texture => { if ( texture . source !== undefined ) { const image = parsedData . images [ texture . source ]; texture . imageUri = image . uri ; } }); } } } // Function to display statistics function displayStats ( parsedData ) { const stats = { numberOfMeshes : parsedData . meshes ? parsedData . meshes . length : 0 , numberOfNodes : parsedData . nodes ? parsedData . nodes . length : 0 , numberOfMaterials : parsedData . materials ? parsedData . materials . length : 0 , numberOfTextures : parsedData . textures ? parsedData . textures . length : 0 , numberOfAnimations : parsedData . animations ? parsedData . animations . length : 0 , }; let details = ` Number of Meshes: ${ stats . numberOfMeshes } <br> Number of Nodes: ${ stats . numberOfNodes } <br> Number of Materials: ${ stats . numberOfMaterials } <br> Number of Textures: ${ stats . numberOfTextures } <br> Number of Animations: ${ stats . numberOfAnimations } <br> <h2>Mesh Details:</h2> `; parsedData . meshes .forEach(( mesh , meshIndex ) => { details += ` Mesh ${ meshIndex } - Name: ${ mesh . name || 'Unnamed' } <br> `; mesh . primitives .forEach(( primitive , primitiveIndex ) => { const verticesCount = primitive . positions ? primitive . positions . length / 3 : 0 ; const indicesCount = primitive . indices ? primitive . indices . length : 0 ; details += ` ${ ' ' . repeat ( 8 )} Primitive ${ primitiveIndex } :<br> ${ ' ' . repeat ( 16 )} Number of Vertices: ${ verticesCount } <br> ${ ' ' . repeat ( 16 )} Number of Indices: ${ indicesCount } <br> `; }); }); const statsDiv = document . createElement ( 'div' ); document . body . appendChild ( statsDiv ); statsDiv . innerHTML = details ; } // Function to load GLTF file and display statistics async function loadGLTFAndShowStats ( url , gltfPath ) { const loader = new glTFLoader (); try { const parsedData = await loader . load ( url , gltfPath ); displayStats ( parsedData ); } catch ( error ) { console . error ( 'An error occurred:' , error ); } } const gltfFileUrl = 'https://webgpulab.xbdev.net/var/resources/gltf/lowpolyworld.gltf' ; const gltfPath = 'https://webgpulab.xbdev.net/var/resources/gltf/' ; let div = document . createElement ( 'div' ); document . body . appendChild ( div ); div . innerHTML = ` Vanilla glTF loader/parser in native JavaScript<br><br> File: ${ gltfFileUrl } <br><br> `; loadGLTFAndShowStats ( gltfFileUrl , gltfPath ); console . log ( 'done..' );
This is what the output for the loader/dump script above looks like:
Resources & Links
• glTF Loader/Dumper LINK
• glTF Tutorials/Code/Examples LINK