utils/gltf/glbParser.tstypescript
import { GltfData, GltfMetadata } from "./types";

interface GlbChunk {
    length: number;
    type: ChunkType;
    chunkData: ArrayBuffer;
}

enum ChunkType {
    JSON,
    BIN,
    INVALID
}

export class Glb {
    public static async load(src: string): Promise<GltfData> {
        const response = await fetch(src);
        if (!response.ok) {
            throw new Error(`Unable to load '${src}'`);
        }

        const buffer = await response.arrayBuffer();

        const dv = new DataView(buffer);
        const magic = dv.getUint32(0, true);

        if (magic !== 0x46546c67) {
            throw new Error("Not a valid GLB");
        }
        const version = dv.getUint32(4, true);
        const chunks = this.getChunks(dv);
        return this.parseChunks(chunks);
    }

    private static getChunks(dv: DataView<ArrayBuffer>): GlbChunk[] {
        const chunks: GlbChunk[] = [];
        let currentOffset = 12; // skip magic, version and total length
        do {
            const chunk = this.getChunk(dv, currentOffset);
            currentOffset += 8 + chunk.length;
            chunks.push(chunk);
        } while (currentOffset < dv.byteLength);
        return chunks;
    }

    private static getChunk(dv: DataView<ArrayBuffer>, offset: number): GlbChunk {
        const length = dv.getUint32(offset, true);
        const type = this.getChunkType(dv.getUint32(offset + 4, true));
        const chunkData = dv.buffer.slice(offset + 8, offset + 8 + length);
        return { length, type, chunkData };
    }

    private static getChunkType(type: number): ChunkType {
        switch (type) {
            case 0x4e4f534a:
                return ChunkType.JSON;
            case 0x004e4942:
                return ChunkType.BIN;
            default:
                return ChunkType.INVALID;
        }
    }

    private static parseChunks(chunks: GlbChunk[]): GltfData {
        let gltf: GltfMetadata | undefined,
            bin: ArrayBuffer[] = [];
        for (const chunk of chunks) {
            switch (chunk.type) {
                case ChunkType.JSON:
                    gltf = this.parseJsonChunk(chunk);
                    break;
                case ChunkType.BIN:
                    bin.push(chunk.chunkData);
                    break;
                default:
                    break;
            }
        }
        if (gltf === undefined) {
            throw new Error("GLB json data extraction failed");
        }
        return {
            gltf,
            bin
        };
    }

    private static parseJsonChunk(chunk: GlbChunk): GltfMetadata {
        return JSON.parse(new TextDecoder().decode(chunk.chunkData));
    }
}