Complete Vanilla JavaScript class for loading and parsing the 3D File format (glTF)
The complete code is given below for the loader/dumper. You can try an online version here (LINK)
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
|