UNPKG

loaders.gl

Version:

Framework-independent loaders for 3D graphics formats

272 lines (214 loc) 9.78 kB
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } /* eslint-disable camelcase, max-statements */ import { getImageSize, padTo4Bytes, copyArrayBuffer, TextEncoder } from '../common/loader-utils'; import { getAccessorType, getAccessorComponentType } from './glb-accessor-utils'; var MAGIC_glTF = 0x676c5446; // glTF in Big-Endian ASCII var LE = true; // Binary GLTF is little endian. var BE = false; // Magic needs to be written as BE var GLB_FILE_HEADER_SIZE = 12; var GLB_CHUNK_HEADER_SIZE = 8; var GLBBuilder = /*#__PURE__*/ function () { function GLBBuilder(rootPath) { _classCallCheck(this, GLBBuilder); // Lets us keep track of how large the body will be, as well as the offset for each of the // original buffers. this.rootPath = rootPath; this.byteLength = 0; this.json = { buffers: [{ // Just the single BIN chunk buffer byteLength: 0 // Updated at end of conversion }], bufferViews: [], accessors: [], images: [] }; // list of binary buffers to be written to the BIN chunk // (Each call to addBuffer, addImage etc adds an entry here) this.sourceBuffers = []; } _createClass(GLBBuilder, [{ key: "getByteLength", value: function getByteLength() { return this.byteLength; } }, { key: "copyToArrayBuffer", value: function copyToArrayBuffer(arrayBuffer, byteOffset) { var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = this.parts[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var _step$value = _step.value, offset = _step$value.offset, contents = _step$value.contents; contents.copy(arrayBuffer, byteOffset + offset); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return != null) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } } // Add a binary buffer. Builds glTF "JSON metadata" and saves buffer reference // Buffer will be copied into BIN chunk during "pack" }, { key: "addBuffer", value: function addBuffer(sourceBuffer) { var accessor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { size: 3 }; var bufferViewIndex = this._addBufferView(sourceBuffer); // Add an accessor pointing to the new buffer view var glTFAccessor = { bufferView: bufferViewIndex, type: getAccessorType(accessor.size), componentType: getAccessorComponentType(sourceBuffer), count: Math.round(sourceBuffer.length / accessor.size) }; this.json.accessors.push(glTFAccessor); return this.json.accessors.length - 1; } // Checks if a binary buffer is a recognized image format (PNG, JPG, GIF, ...) }, { key: "isImage", value: function isImage(imageData) { try { getImageSize(imageData); return true; } catch (error) { return false; } } // Adds a binary image. Builds glTF "JSON metadata" and saves buffer reference // Buffer will be copied into BIN chunk during "pack" }, { key: "addImage", value: function addImage(imageData) { var bufferViewIndex = this._addBufferView(imageData); var glTFImage = { bufferView: bufferViewIndex }; // Get the properties of the image to add as metadata. var sizeAndType = getImageSize(imageData); if (sizeAndType) { var mimeType = sizeAndType.mimeType, width = sizeAndType.width, height = sizeAndType.height; Object.assign(glTFImage, { mimeType: mimeType, width: width, height: height }); } this.json.images.push(glTFImage); return this.json.images.length - 1; } }, { key: "pack", value: function pack() { this._packBinaryChunk(); return { arrayBuffer: this.arrayBuffer, json: this.json }; } }, { key: "encode", value: function encode(json) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return this._createGlbBuffer(json, options); } // PRIVATE // Add one source buffer, create a matchibng glTF `bufferView`, and return its index }, { key: "_addBufferView", value: function _addBufferView(buffer) { var byteLength = buffer.byteLength || buffer.length; // Add a bufferView indicating start and length of this binary sub-chunk this.json.bufferViews.push({ buffer: 0, // Write offset from the start of the binary body byteOffset: this.byteLength, byteLength: byteLength }); // We've now written the contents to the body, so update the total length // Every sub-chunk needs to be 4-byte aligned this.byteLength += padTo4Bytes(byteLength); // Add this buffer to the list of buffers to be written to the body. this.sourceBuffers.push(buffer); // Return the index to the just created bufferView return this.json.bufferViews.length - 1; } // Pack the binary chunk }, { key: "_packBinaryChunk", value: function _packBinaryChunk() { // Already packed if (this.arrayBuffer) { return; } // Allocate total array var totalByteLength = this.byteLength; var arrayBuffer = new ArrayBuffer(totalByteLength); var targetArray = new Uint8Array(arrayBuffer); // Copy each array into var byteOffset = 0; for (var i = 0; i < this.sourceBuffers.length; i++) { var sourceBuffer = this.sourceBuffers[i]; var byteLength = sourceBuffer.byteLength; // Pack buffer onto the big target array var sourceArray = new Uint8Array(sourceBuffer.buffer); targetArray.set(sourceArray, byteOffset); byteOffset += padTo4Bytes(byteLength); } // Update the glTF BIN CHUNK byte length this.json.buffers[0].byteLength = totalByteLength; // Save generated arrayBuffer this.arrayBuffer = arrayBuffer; // Clear out sourceBuffers this.sourceBuffers = []; } // Encode the full GLB buffer with header etc // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0# // glb-file-format-specification }, { key: "_createGlbBuffer", value: function _createGlbBuffer(appJson) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; // TODO - avoid double array buffer creation this._packBinaryChunk(); this.json.json = appJson; var binChunk = this.arrayBuffer; if (options.magic) { console.warn('Custom glTF magic number no longer supported'); // eslint-disable-line } var jsonChunkOffset = GLB_FILE_HEADER_SIZE + GLB_CHUNK_HEADER_SIZE; // First headers: 20 bytes var jsonChunk = convertObjectToJsonChunk(this.json); // As body is 4-byte aligned, the scene length must be padded to have a multiple of 4. var jsonChunkLength = padTo4Bytes(jsonChunk.byteLength); var binChunkOffset = jsonChunkLength + jsonChunkOffset; var fileLength = binChunkOffset + GLB_CHUNK_HEADER_SIZE + padTo4Bytes(binChunk.byteLength); // Length is know, we can create the GLB memory buffer! var glbArrayBuffer = new ArrayBuffer(fileLength); var dataView = new DataView(glbArrayBuffer); // GLB Header dataView.setUint32(0, MAGIC_glTF, BE); // Magic number (the ASCII string 'glTF'). dataView.setUint32(4, 2, LE); // Version 2 of binary glTF container format uint32 dataView.setUint32(8, fileLength, LE); // Total byte length of generated file (uint32) // Write the JSON chunk dataView.setUint32(12, jsonChunk.byteLength, LE); // Byte length of json chunk (uint32) dataView.setUint32(16, 0, LE); // Chunk format as uint32 (JSON is 0) copyArrayBuffer(glbArrayBuffer, jsonChunk, jsonChunkOffset); // TODO - Add spaces as padding to ensure scene is a multiple of 4 bytes. // for (let i = jsonChunkLength + 20; i < binChunkOffset; ++i) { // glbFileArray[i] = 0x20; // } // Write the BIN chunk var binChunkLengthPadded = padTo4Bytes(binChunk.byteLength); dataView.setUint32(binChunkOffset + 0, binChunkLengthPadded, LE); // Byte length BIN (uint32) dataView.setUint32(binChunkOffset + 4, 1, LE); // Chunk format as uint32 (BIN is 1) copyArrayBuffer(glbArrayBuffer, binChunk, binChunkOffset + GLB_CHUNK_HEADER_SIZE); return glbArrayBuffer; } }]); return GLBBuilder; }(); export { GLBBuilder as default }; function convertObjectToJsonChunk(json) { var jsonChunkString = JSON.stringify(json); var textEncoder = new TextEncoder('utf8'); return textEncoder.encode(jsonChunkString); } //# sourceMappingURL=glb-builder.js.map