├── .appveyor.yml ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmignore ├── .npmrc ├── .prettierignore ├── CHANGES.md ├── LICENSE.md ├── README.md ├── ThirdParty.json ├── bin └── gltf-pipeline.js ├── doc └── gltf.png ├── gulpfile.js ├── index.js ├── lib ├── FileUrl.js ├── ForEach.js ├── addBuffer.js ├── addDefaults.js ├── addExtensionsRequired.js ├── addExtensionsUsed.js ├── addPipelineExtras.js ├── addToArray.js ├── compressDracoMeshes.js ├── dataUriToBuffer.js ├── findAccessorMinMax.js ├── forEachTextureInMaterial.js ├── getAccessorByteStride.js ├── getBufferPadded.js ├── getComponentReader.js ├── getImageExtension.js ├── getJsonBufferPadded.js ├── getStatistics.js ├── glbToGltf.js ├── gltfToGlb.js ├── mergeBuffers.js ├── moveTechniqueRenderStates.js ├── moveTechniquesToExtension.js ├── numberOfComponentsForType.js ├── parseGlb.js ├── processGlb.js ├── processGltf.js ├── readAccessorPacked.js ├── readResources.js ├── removeDefaults.js ├── removeExtension.js ├── removeExtensionsRequired.js ├── removeExtensionsUsed.js ├── removePipelineExtras.js ├── removeUnusedElements.js ├── replaceWithDecompressedPrimitive.js ├── splitPrimitives.js ├── updateAccessorComponentTypes.js ├── updateVersion.js ├── usesExtension.js └── writeResources.js ├── package.json └── specs ├── .eslintrc.json ├── data ├── 1.0 │ ├── box-materials-common │ │ ├── box-materials-common.bin │ │ └── box-materials-common.gltf │ ├── box-textured-binary-separate │ │ ├── box-textured-binary-separate-fs.glsl │ │ ├── box-textured-binary-separate-vs.glsl │ │ ├── box-textured-binary-separate.glb │ │ └── cesium.png │ ├── box-textured-binary │ │ └── box-textured-binary.glb │ ├── box-textured-embedded │ │ └── box-textured-embedded.gltf │ ├── box-textured-materials-common │ │ ├── CesiumLogoFlat.png │ │ ├── box-textured-materials-common.bin │ │ └── box-textured-materials-common.gltf │ ├── box-textured-separate │ │ ├── box-textured-separate-fs.glsl │ │ ├── box-textured-separate-vs.glsl │ │ ├── box-textured-separate.bin │ │ ├── box-textured-separate.gltf │ │ └── cesium.png │ └── box │ │ └── box.gltf └── 2.0 │ ├── box-morph │ └── box-morph.gltf │ ├── box-shared-image-references-separate │ ├── CesiumTexturedBoxTest0FS.glsl │ ├── CesiumTexturedBoxTest0VS.glsl │ ├── Image.png │ ├── box-shared-image-references-separate.bin │ └── box-shared-image-references-separate.gltf │ ├── box-shared-image-references │ └── box-shared-image-references.gltf │ ├── box-techniques-embedded │ └── box-techniques-embedded.gltf │ ├── box-techniques-separate │ ├── CesiumTexturedBoxTest.bin │ ├── CesiumTexturedBoxTest0FS.glsl │ ├── CesiumTexturedBoxTest0VS.glsl │ ├── Image0001.png │ └── box-techniques-separate.gltf │ ├── box-textured-binary-separate │ ├── box-textured-binary-separate.glb │ └── cesium.png │ ├── box-textured-binary │ └── box-textured-binary.glb │ ├── box-textured-embedded │ └── box-textured-embedded.gltf │ ├── box-textured-separate │ ├── box-textured-separate.bin │ ├── box-textured-separate.gltf │ └── cesium logo.png │ ├── extensions │ ├── EXT_meshopt_compression │ │ ├── meshopt-fallback │ │ │ ├── meshopt-fallback.bin │ │ │ ├── meshopt-fallback.fallback.bin │ │ │ └── meshopt-fallback.gltf │ │ └── meshopt-no-fallback │ │ │ ├── meshopt-no-fallback.bin │ │ │ └── meshopt-no-fallback.gltf │ └── EXT_texture_webp │ │ ├── box-textured-embedded │ │ └── box-textured-embedded.gltf │ │ └── box-textured-separate │ │ ├── box-textured-separate.bin │ │ ├── box-textured-separate.gltf │ │ ├── box-textured-with-fallback.gltf │ │ ├── cesium logo.png │ │ └── cesium logo.webp │ ├── multiple-boxes │ └── multiple-boxes.gltf │ └── triangle-without-indices │ └── triangle-without-indices.gltf ├── jasmine.json └── lib ├── ForEachSpec.js ├── addBufferSpec.js ├── addDefaultsSpec.js ├── addExtensionsRequiredSpec.js ├── addExtensionsUsedSpec.js ├── addPipelineExtrasSpec.js ├── addToArraySpec.js ├── compressDracoMeshesSpec.js ├── dataUriToBufferSpec.js ├── findAccessorMinMaxSpec.js ├── getAccessorByteStrideSpec.js ├── getBufferPaddedSpec.js ├── getComponentReaderSpec.js ├── getImageExtensionSpec.js ├── getJsonBufferPaddedSpec.js ├── getStatisticsSpec.js ├── glbToGltfSpec.js ├── gltfToGlbSpec.js ├── mergeBuffersSpec.js ├── moveTechniqueRenderStatesSpec.js ├── moveTechniquesToExtensionSpec.js ├── numberOfComponentsForTypeSpec.js ├── parseGlbSpec.js ├── processGlbSpec.js ├── processGltfSpec.js ├── readAccessorPackedSpec.js ├── readResourcesSpec.js ├── removeDefaultsSpec.js ├── removeExtensionSpec.js ├── removeExtensionsRequiredSpec.js ├── removeExtensionsUsedSpec.js ├── removePipelineExtrasSpec.js ├── removeUnusedElementsSpec.js ├── splitPrimitivesSpec.js ├── updateAccessorComponentTypeSpec.js ├── updateVersionSpec.js ├── usesExtensionSpec.js └── writeResourcesSpec.js /.appveyor.yml: -------------------------------------------------------------------------------- 1 | # Builds are whole numbered 2 | version: '{build}' 3 | 4 | clone_depth: 50 5 | 6 | environment: 7 | matrix: 8 | - nodejs_version: "12" 9 | 10 | install: 11 | # Get the latest stable version of Node.js 12 | - ps: Install-Product node $env:nodejs_version 13 | # install modules 14 | - npm install 15 | 16 | test_script: 17 | - node --version 18 | - npm --version 19 | - npm run eslint 20 | - npm run test -- --failTaskOnError --suppressPassed 21 | 22 | # Don't actually build. 23 | build: off 24 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | coverage/** 3 | doc/** 4 | dist/** 5 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["cesium/node"], 3 | "rules": { 4 | "no-unused-vars": ["error", {"args": "none"}] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | package.json text eol=lf -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | run-name: build 2 | on: 3 | push: 4 | concurrency: 5 | group: ${{ github.ref }} 6 | cancel-in-progress: true 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node: [ 16, 20 ] 13 | name: Node ${{ matrix.node }} 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions/setup-node@v3 17 | with: 18 | node-version: ${{ matrix.node }} 19 | - run: npm install 20 | - run: npm run eslint 21 | - run: npm run prettier-check 22 | - run: npm run test -- --failTaskOnError --suppressPassed 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # NPM 2 | node_modules 3 | npm-debug.log 4 | package-lock.json 5 | yarn.lock 6 | 7 | # WebStorm user-specific 8 | .idea/workspace.xml 9 | .idea/tasks.xml 10 | 11 | # VSCode user-specific 12 | .vscode 13 | 14 | # Generate data 15 | .eslintcache 16 | coverage 17 | dist 18 | output 19 | test 20 | *.tgz 21 | .nyc_output 22 | doc/* 23 | !doc/gltf.png 24 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run pre-commit 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.husky 2 | /.vscode 3 | /.idea 4 | /dist 5 | /coverage 6 | /doc/* 7 | /output 8 | /specs 9 | /test 10 | .editorconfig 11 | .eslintcache 12 | .eslintignore 13 | .eslintrc.json 14 | .gitattributes 15 | .nyc_output 16 | .npmignore 17 | .prettierignore 18 | .travis.yml 19 | .appveyor.yml 20 | gulpfile.js 21 | *.tgz 22 | !doc/gltf.png 23 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | 4 | # Unignore directories (to all depths) and unignore code in these directories 5 | !bin/**/ 6 | !doc/**/ 7 | !lib/**/ 8 | !specs/**/ 9 | 10 | !**/*.js 11 | !**/*.md 12 | -------------------------------------------------------------------------------- /ThirdParty.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "bluebird", 4 | "license": [ 5 | "MIT" 6 | ], 7 | "version": "3.7.2", 8 | "url": "https://www.npmjs.com/package/bluebird" 9 | }, 10 | { 11 | "name": "cesium", 12 | "license": [ 13 | "Apache-2.0" 14 | ], 15 | "version": "1.109.0", 16 | "url": "https://www.npmjs.com/package/cesium" 17 | }, 18 | { 19 | "name": "draco3d", 20 | "license": [ 21 | "Apache-2.0" 22 | ], 23 | "version": "1.5.6", 24 | "url": "https://www.npmjs.com/package/draco3d" 25 | }, 26 | { 27 | "name": "fs-extra", 28 | "license": [ 29 | "MIT" 30 | ], 31 | "version": "11.1.1", 32 | "url": "https://www.npmjs.com/package/fs-extra" 33 | }, 34 | { 35 | "name": "mime", 36 | "license": [ 37 | "MIT" 38 | ], 39 | "version": "3.0.0", 40 | "url": "https://www.npmjs.com/package/mime" 41 | }, 42 | { 43 | "name": "object-hash", 44 | "license": [ 45 | "MIT" 46 | ], 47 | "version": "3.0.0", 48 | "url": "https://www.npmjs.com/package/object-hash" 49 | }, 50 | { 51 | "name": "yargs", 52 | "license": [ 53 | "MIT" 54 | ], 55 | "version": "17.7.2", 56 | "url": "https://www.npmjs.com/package/yargs" 57 | } 58 | ] -------------------------------------------------------------------------------- /doc/gltf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/doc/gltf.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable n/global-require*/ 2 | "use strict"; 3 | module.exports = { 4 | getStatistics: require("./lib/getStatistics"), 5 | glbToGltf: require("./lib/glbToGltf"), 6 | gltfToGlb: require("./lib/gltfToGlb"), 7 | processGlb: require("./lib/processGlb"), 8 | processGltf: require("./lib/processGltf"), 9 | removeExtension: require("./lib/removeExtension"), 10 | }; 11 | -------------------------------------------------------------------------------- /lib/FileUrl.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { Check, RuntimeError } = require("cesium"); 4 | const os = require("os"); 5 | const path = require("path"); 6 | const { domainToUnicode, URL } = require("url"); 7 | 8 | module.exports = { 9 | fileURLToPath: fileURLToPath, 10 | pathToFileURL: pathToFileURL, 11 | }; 12 | 13 | const isWindows = os.platform() === "win32"; 14 | const forwardSlashRegEx = /\//g; 15 | 16 | const CHAR_LOWERCASE_A = 97; 17 | const CHAR_LOWERCASE_Z = 122; 18 | const CHAR_FORWARD_SLASH = 47; 19 | const CHAR_BACKWARD_SLASH = 92; 20 | 21 | const percentRegEx = /%/g; 22 | const backslashRegEx = /\\/g; 23 | const newlineRegEx = /\n/g; 24 | const carriageReturnRegEx = /\r/g; 25 | const tabRegEx = /\t/g; 26 | 27 | // The following function is copied from Node.js implementation of url module 28 | // https://github.com/nodejs/node/blob/7237eaa3353aacf284289c8b59b0a5e0fa5744bb/lib/internal/url.js#L1345-L1383 29 | // pathToFileURL & fileURLToPath were added in 10.12 so we want to maintain ability run under older versions. 30 | function fileURLToPath(path) { 31 | Check.defined("path", path); 32 | 33 | if (typeof path === "string") { 34 | path = new URL(path); 35 | } 36 | 37 | if (path.protocol !== "file:") { 38 | throw new RuntimeError("Expected path.protocol to start with file:"); 39 | } 40 | return isWindows ? getPathFromURLWin32(path) : getPathFromURLPosix(path); 41 | } 42 | 43 | function pathToFileURL(filepath) { 44 | let resolved = path.resolve(filepath); 45 | // path.resolve strips trailing slashes so we must add them back 46 | const filePathLast = filepath.charCodeAt(filepath.length - 1); 47 | if ( 48 | (filePathLast === CHAR_FORWARD_SLASH || 49 | (isWindows && filePathLast === CHAR_BACKWARD_SLASH)) && 50 | resolved[resolved.length - 1] !== path.sep 51 | ) { 52 | resolved += "/"; 53 | } 54 | const outURL = new URL("file://"); 55 | if (resolved.includes("%")) { 56 | resolved = resolved.replace(percentRegEx, "%25"); 57 | } 58 | // in posix, "/" is a valid character in paths 59 | if (!isWindows && resolved.includes("\\")) { 60 | resolved = resolved.replace(backslashRegEx, "%5C"); 61 | } 62 | if (resolved.includes("\n")) { 63 | resolved = resolved.replace(newlineRegEx, "%0A"); 64 | } 65 | if (resolved.includes("\r")) { 66 | resolved = resolved.replace(carriageReturnRegEx, "%0D"); 67 | } 68 | if (resolved.includes("\t")) { 69 | resolved = resolved.replace(tabRegEx, "%09"); 70 | } 71 | outURL.pathname = resolved; 72 | return outURL; 73 | } 74 | 75 | function getPathFromURLWin32(url) { 76 | const hostname = url.hostname; 77 | let pathname = url.pathname; 78 | for (let n = 0; n < pathname.length; n++) { 79 | if (pathname[n] === "%") { 80 | const third = pathname.codePointAt(n + 2) | 0x20; 81 | if ( 82 | (pathname[n + 1] === "2" && third === 102) || // 2f 2F / 83 | (pathname[n + 1] === "5" && third === 99) 84 | ) { 85 | // 5c 5C \ 86 | throw new RuntimeError( 87 | "file URL must not include encoded \\ or / characters", 88 | ); 89 | } 90 | } 91 | } 92 | pathname = pathname.replace(forwardSlashRegEx, "\\"); 93 | pathname = decodeURIComponent(pathname); 94 | if (hostname !== "") { 95 | // If hostname is set, then we have a UNC path 96 | // Pass the hostname through domainToUnicode just in case 97 | // it is an IDN using punycode encoding. We do not need to worry 98 | // about percent encoding because the URL parser will have 99 | // already taken care of that for us. Note that this only 100 | // causes IDNs with an appropriate `xn--` prefix to be decoded. 101 | return `\\\\${domainToUnicode(hostname)}${pathname}`; 102 | } 103 | 104 | // Otherwise, it's a local path that requires a drive letter 105 | const letter = pathname.codePointAt(1) | 0x20; 106 | const sep = pathname[2]; 107 | if ( 108 | letter < CHAR_LOWERCASE_A || 109 | letter > CHAR_LOWERCASE_Z || // a..z A..Z 110 | sep !== ":" 111 | ) { 112 | throw new RuntimeError("file URL must be absolute"); 113 | } 114 | return pathname.slice(1); 115 | } 116 | 117 | function getPathFromURLPosix(url) { 118 | if (url.hostname !== "") { 119 | throw new RuntimeError("Invalid platform"); 120 | } 121 | const pathname = url.pathname; 122 | for (let n = 0; n < pathname.length; n++) { 123 | if (pathname[n] === "%") { 124 | const third = pathname.codePointAt(n + 2) | 0x20; 125 | if (pathname[n + 1] === "2" && third === 102) { 126 | throw new RuntimeError( 127 | "file URL must not include encoded \\ or / characters", 128 | ); 129 | } 130 | } 131 | } 132 | return decodeURIComponent(pathname); 133 | } 134 | -------------------------------------------------------------------------------- /lib/addBuffer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const addToArray = require("./addToArray"); 3 | 4 | module.exports = addBuffer; 5 | 6 | /** 7 | * Adds buffer to gltf. 8 | * 9 | * @param {object} gltf A javascript object containing a glTF asset. 10 | * @param {Buffer} buffer A Buffer object which will be added to gltf.buffers. 11 | * @returns {number} The bufferView id of the newly added bufferView. 12 | * 13 | * @private 14 | */ 15 | function addBuffer(gltf, buffer) { 16 | const newBuffer = { 17 | byteLength: buffer.length, 18 | extras: { 19 | _pipeline: { 20 | source: buffer, 21 | }, 22 | }, 23 | }; 24 | const bufferId = addToArray(gltf.buffers, newBuffer); 25 | const bufferView = { 26 | buffer: bufferId, 27 | byteOffset: 0, 28 | byteLength: buffer.length, 29 | }; 30 | return addToArray(gltf.bufferViews, bufferView); 31 | } 32 | -------------------------------------------------------------------------------- /lib/addExtensionsRequired.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const addExtensionsUsed = require("./addExtensionsUsed"); 4 | const addToArray = require("./addToArray"); 5 | 6 | const defined = Cesium.defined; 7 | 8 | module.exports = addExtensionsRequired; 9 | 10 | /** 11 | * Adds an extension to gltf.extensionsRequired if it does not already exist. 12 | * Initializes extensionsRequired if it is not defined. 13 | * 14 | * @param {object} gltf A javascript object containing a glTF asset. 15 | * @param {string} extension The extension to add. 16 | * 17 | * @private 18 | */ 19 | function addExtensionsRequired(gltf, extension) { 20 | let extensionsRequired = gltf.extensionsRequired; 21 | if (!defined(extensionsRequired)) { 22 | extensionsRequired = []; 23 | gltf.extensionsRequired = extensionsRequired; 24 | } 25 | addToArray(extensionsRequired, extension, true); 26 | addExtensionsUsed(gltf, extension); 27 | } 28 | -------------------------------------------------------------------------------- /lib/addExtensionsUsed.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const addToArray = require("./addToArray"); 4 | 5 | const defined = Cesium.defined; 6 | 7 | module.exports = addExtensionsUsed; 8 | 9 | /** 10 | * Adds an extension to gltf.extensionsUsed if it does not already exist. 11 | * Initializes extensionsUsed if it is not defined. 12 | * 13 | * @param {object} gltf A javascript object containing a glTF asset. 14 | * @param {string} extension The extension to add. 15 | * 16 | * @private 17 | */ 18 | function addExtensionsUsed(gltf, extension) { 19 | let extensionsUsed = gltf.extensionsUsed; 20 | if (!defined(extensionsUsed)) { 21 | extensionsUsed = []; 22 | gltf.extensionsUsed = extensionsUsed; 23 | } 24 | addToArray(extensionsUsed, extension, true); 25 | } 26 | -------------------------------------------------------------------------------- /lib/addPipelineExtras.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const ForEach = require("./ForEach"); 4 | 5 | const defined = Cesium.defined; 6 | 7 | module.exports = addPipelineExtras; 8 | 9 | /** 10 | * Adds extras._pipeline to each object that can have extras in the glTF asset. 11 | * This stage runs before updateVersion and handles both glTF 1.0 and glTF 2.0 assets. 12 | * 13 | * @param {object} gltf A javascript object containing a glTF asset. 14 | * @returns {object} The glTF asset with the added pipeline extras. 15 | * 16 | * @private 17 | */ 18 | function addPipelineExtras(gltf) { 19 | ForEach.shader(gltf, function (shader) { 20 | addExtras(shader); 21 | }); 22 | ForEach.buffer(gltf, function (buffer) { 23 | addExtras(buffer); 24 | }); 25 | ForEach.image(gltf, function (image) { 26 | addExtras(image); 27 | }); 28 | 29 | addExtras(gltf); 30 | 31 | return gltf; 32 | } 33 | 34 | function addExtras(object) { 35 | object.extras = defined(object.extras) ? object.extras : {}; 36 | object.extras._pipeline = defined(object.extras._pipeline) 37 | ? object.extras._pipeline 38 | : {}; 39 | } 40 | -------------------------------------------------------------------------------- /lib/addToArray.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | 4 | const defaultValue = Cesium.defaultValue; 5 | 6 | module.exports = addToArray; 7 | 8 | /** 9 | * Adds an element to an array and returns the element's index. 10 | * 11 | * @param {Array} array The array to add to. 12 | * @param {object} element The element to add. 13 | * @param {boolean} [checkDuplicates=false] When true, if a duplicate element is found its index is returned and element is not added to the array. 14 | * 15 | * @private 16 | */ 17 | function addToArray(array, element, checkDuplicates) { 18 | checkDuplicates = defaultValue(checkDuplicates, false); 19 | if (checkDuplicates) { 20 | const index = array.indexOf(element); 21 | if (index > -1) { 22 | return index; 23 | } 24 | } 25 | 26 | array.push(element); 27 | return array.length - 1; 28 | } 29 | -------------------------------------------------------------------------------- /lib/dataUriToBuffer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | module.exports = dataUriToBuffer; 3 | 4 | /** 5 | * Read a data uri string into a buffer. 6 | * 7 | * @param {string} dataUri The data uri. 8 | * @returns {Buffer} 9 | * 10 | * @private 11 | */ 12 | function dataUriToBuffer(dataUri) { 13 | const data = dataUri.slice(dataUri.indexOf(",") + 1); 14 | if (dataUri.indexOf("base64") >= 0) { 15 | return Buffer.from(data, "base64"); 16 | } 17 | return Buffer.from(data, "utf8"); 18 | } 19 | -------------------------------------------------------------------------------- /lib/findAccessorMinMax.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | 4 | const ComponentDatatype = Cesium.ComponentDatatype; 5 | const defined = Cesium.defined; 6 | 7 | const getAccessorByteStride = require("./getAccessorByteStride"); 8 | const getComponentReader = require("./getComponentReader"); 9 | const numberOfComponentsForType = require("./numberOfComponentsForType"); 10 | 11 | module.exports = findAccessorMinMax; 12 | 13 | /** 14 | * Finds the min and max values of the accessor. 15 | * 16 | * @param {object} gltf A javascript object containing a glTF asset. 17 | * @param {object} accessor The accessor object from the glTF asset to read. 18 | * @returns {{min: Array, max: Array}} min holding the array of minimum values and max holding the array of maximum values. 19 | * 20 | * @private 21 | */ 22 | function findAccessorMinMax(gltf, accessor) { 23 | const bufferViews = gltf.bufferViews; 24 | const buffers = gltf.buffers; 25 | const bufferViewId = accessor.bufferView; 26 | const numberOfComponents = numberOfComponentsForType(accessor.type); 27 | 28 | // According to the spec, when bufferView is not defined, accessor must be initialized with zeros 29 | if (!defined(accessor.bufferView)) { 30 | return { 31 | min: new Array(numberOfComponents).fill(0.0), 32 | max: new Array(numberOfComponents).fill(0.0), 33 | }; 34 | } 35 | 36 | const min = new Array(numberOfComponents).fill(Number.POSITIVE_INFINITY); 37 | const max = new Array(numberOfComponents).fill(Number.NEGATIVE_INFINITY); 38 | 39 | const bufferView = bufferViews[bufferViewId]; 40 | const bufferId = bufferView.buffer; 41 | const buffer = buffers[bufferId]; 42 | const source = buffer.extras._pipeline.source; 43 | 44 | const count = accessor.count; 45 | const byteStride = getAccessorByteStride(gltf, accessor); 46 | let byteOffset = 47 | accessor.byteOffset + bufferView.byteOffset + source.byteOffset; 48 | const componentType = accessor.componentType; 49 | const componentTypeByteLength = 50 | ComponentDatatype.getSizeInBytes(componentType); 51 | const dataView = new DataView(source.buffer); 52 | const components = new Array(numberOfComponents); 53 | const componentReader = getComponentReader(componentType); 54 | 55 | for (let i = 0; i < count; i++) { 56 | componentReader( 57 | dataView, 58 | byteOffset, 59 | numberOfComponents, 60 | componentTypeByteLength, 61 | components, 62 | ); 63 | for (let j = 0; j < numberOfComponents; j++) { 64 | const value = components[j]; 65 | min[j] = Math.min(min[j], value); 66 | max[j] = Math.max(max[j], value); 67 | } 68 | byteOffset += byteStride; 69 | } 70 | 71 | return { 72 | min: min, 73 | max: max, 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /lib/forEachTextureInMaterial.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | 4 | const ForEach = require("./ForEach"); 5 | 6 | const Check = Cesium.Check; 7 | const defined = Cesium.defined; 8 | 9 | module.exports = forEachTextureInMaterial; 10 | 11 | /** 12 | * Calls the provider handler function on each texture used by the material. 13 | * Mimics the behavior of functions in gltf-pipeline ForEach. 14 | * @param {object} material The glTF material. 15 | * @param {forEachTextureInMaterial~handler} handler Function that is called for each texture in the material. 16 | * 17 | * @private 18 | */ 19 | function forEachTextureInMaterial(material, handler) { 20 | Check.typeOf.object("material", material); 21 | Check.defined("handler", handler); 22 | 23 | // Metallic roughness 24 | const pbrMetallicRoughness = material.pbrMetallicRoughness; 25 | if (defined(pbrMetallicRoughness)) { 26 | if (defined(pbrMetallicRoughness.baseColorTexture)) { 27 | const textureInfo = pbrMetallicRoughness.baseColorTexture; 28 | const value = handler(textureInfo.index, textureInfo); 29 | if (defined(value)) { 30 | return value; 31 | } 32 | } 33 | if (defined(pbrMetallicRoughness.metallicRoughnessTexture)) { 34 | const textureInfo = pbrMetallicRoughness.metallicRoughnessTexture; 35 | const value = handler(textureInfo.index, textureInfo); 36 | if (defined(value)) { 37 | return value; 38 | } 39 | } 40 | } 41 | 42 | if (defined(material.extensions)) { 43 | // Spec gloss extension 44 | const pbrSpecularGlossiness = 45 | material.extensions.KHR_materials_pbrSpecularGlossiness; 46 | if (defined(pbrSpecularGlossiness)) { 47 | if (defined(pbrSpecularGlossiness.diffuseTexture)) { 48 | const textureInfo = pbrSpecularGlossiness.diffuseTexture; 49 | const value = handler(textureInfo.index, textureInfo); 50 | if (defined(value)) { 51 | return value; 52 | } 53 | } 54 | if (defined(pbrSpecularGlossiness.specularGlossinessTexture)) { 55 | const textureInfo = pbrSpecularGlossiness.specularGlossinessTexture; 56 | const value = handler(textureInfo.index, textureInfo); 57 | if (defined(value)) { 58 | return value; 59 | } 60 | } 61 | } 62 | 63 | // Materials common extension (may be present in models converted from glTF 1.0) 64 | const materialsCommon = material.extensions.KHR_materials_common; 65 | if (defined(materialsCommon) && defined(materialsCommon.values)) { 66 | const diffuse = materialsCommon.values.diffuse; 67 | const ambient = materialsCommon.values.ambient; 68 | const emission = materialsCommon.values.emission; 69 | const specular = materialsCommon.values.specular; 70 | if (defined(diffuse) && defined(diffuse.index)) { 71 | const value = handler(diffuse.index, diffuse); 72 | if (defined(value)) { 73 | return value; 74 | } 75 | } 76 | if (defined(ambient) && defined(ambient.index)) { 77 | const value = handler(ambient.index, ambient); 78 | if (defined(value)) { 79 | return value; 80 | } 81 | } 82 | if (defined(emission) && defined(emission.index)) { 83 | const value = handler(emission.index, emission); 84 | if (defined(value)) { 85 | return value; 86 | } 87 | } 88 | if (defined(specular) && defined(specular.index)) { 89 | const value = handler(specular.index, specular); 90 | if (defined(value)) { 91 | return value; 92 | } 93 | } 94 | } 95 | } 96 | 97 | // KHR_techniques_webgl extension 98 | const value = ForEach.materialValue(material, function (materialValue) { 99 | if (defined(materialValue.index)) { 100 | const value = handler(materialValue.index, materialValue); 101 | if (defined(value)) { 102 | return value; 103 | } 104 | } 105 | }); 106 | if (defined(value)) { 107 | return value; 108 | } 109 | 110 | // Top level textures 111 | if (defined(material.emissiveTexture)) { 112 | const textureInfo = material.emissiveTexture; 113 | const value = handler(textureInfo.index, textureInfo); 114 | if (defined(value)) { 115 | return value; 116 | } 117 | } 118 | 119 | if (defined(material.normalTexture)) { 120 | const textureInfo = material.normalTexture; 121 | const value = handler(textureInfo.index, textureInfo); 122 | if (defined(value)) { 123 | return value; 124 | } 125 | } 126 | 127 | if (defined(material.occlusionTexture)) { 128 | const textureInfo = material.occlusionTexture; 129 | const value = handler(textureInfo.index, textureInfo); 130 | if (defined(value)) { 131 | return value; 132 | } 133 | } 134 | } 135 | 136 | /** 137 | * Function that is called for each texture in the material. If this function returns a value the for each stops and returns that value. 138 | * @callback forEachTextureInMaterial~handler 139 | * @param {number} The texture index. 140 | * @param {object} The texture info object. 141 | * 142 | * @private 143 | */ 144 | -------------------------------------------------------------------------------- /lib/getAccessorByteStride.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const numberOfComponentsForType = require("./numberOfComponentsForType"); 4 | 5 | const ComponentDatatype = Cesium.ComponentDatatype; 6 | const defined = Cesium.defined; 7 | 8 | module.exports = getAccessorByteStride; 9 | 10 | /** 11 | * Returns the byte stride of the provided accessor. 12 | * If the byteStride is 0, it is calculated based on type and componentType 13 | * 14 | * @param {object} gltf A javascript object containing a glTF asset. 15 | * @param {object} accessor The accessor. 16 | * @returns {number} The byte stride of the accessor. 17 | * 18 | * @private 19 | */ 20 | function getAccessorByteStride(gltf, accessor) { 21 | const bufferViewId = accessor.bufferView; 22 | if (defined(bufferViewId)) { 23 | const bufferView = gltf.bufferViews[bufferViewId]; 24 | if (defined(bufferView.byteStride) && bufferView.byteStride > 0) { 25 | return bufferView.byteStride; 26 | } 27 | } 28 | return ( 29 | ComponentDatatype.getSizeInBytes(accessor.componentType) * 30 | numberOfComponentsForType(accessor.type) 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /lib/getBufferPadded.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | module.exports = getBufferPadded; 3 | 4 | /** 5 | * Pad the buffer to the next 8-byte boundary to ensure proper alignment for the section that follows. 6 | * glTF only requires 4-byte alignment but some extensions like EXT_structural_metadata require 8-byte 7 | * alignment for some buffer views. 8 | * 9 | * @param {Buffer} buffer The buffer. 10 | * @returns {Buffer} The padded buffer. 11 | * 12 | * @private 13 | */ 14 | function getBufferPadded(buffer) { 15 | const boundary = 8; 16 | const byteLength = buffer.length; 17 | const remainder = byteLength % boundary; 18 | if (remainder === 0) { 19 | return buffer; 20 | } 21 | const padding = remainder === 0 ? 0 : boundary - remainder; 22 | const emptyBuffer = Buffer.alloc(padding); 23 | return Buffer.concat([buffer, emptyBuffer]); 24 | } 25 | -------------------------------------------------------------------------------- /lib/getComponentReader.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | 4 | const ComponentDatatype = Cesium.ComponentDatatype; 5 | 6 | module.exports = getComponentReader; 7 | 8 | /** 9 | * Returns a function to read and convert data from a DataView into an array. 10 | * 11 | * @param {number} componentType Type to convert the data to. 12 | * @returns {ComponentReader} Function that reads and converts data. 13 | * 14 | * @private 15 | */ 16 | function getComponentReader(componentType) { 17 | switch (componentType) { 18 | case ComponentDatatype.BYTE: 19 | return function ( 20 | dataView, 21 | byteOffset, 22 | numberOfComponents, 23 | componentTypeByteLength, 24 | result, 25 | ) { 26 | for (let i = 0; i < numberOfComponents; ++i) { 27 | result[i] = dataView.getInt8( 28 | byteOffset + i * componentTypeByteLength, 29 | ); 30 | } 31 | }; 32 | case ComponentDatatype.UNSIGNED_BYTE: 33 | return function ( 34 | dataView, 35 | byteOffset, 36 | numberOfComponents, 37 | componentTypeByteLength, 38 | result, 39 | ) { 40 | for (let i = 0; i < numberOfComponents; ++i) { 41 | result[i] = dataView.getUint8( 42 | byteOffset + i * componentTypeByteLength, 43 | ); 44 | } 45 | }; 46 | case ComponentDatatype.SHORT: 47 | return function ( 48 | dataView, 49 | byteOffset, 50 | numberOfComponents, 51 | componentTypeByteLength, 52 | result, 53 | ) { 54 | for (let i = 0; i < numberOfComponents; ++i) { 55 | result[i] = dataView.getInt16( 56 | byteOffset + i * componentTypeByteLength, 57 | true, 58 | ); 59 | } 60 | }; 61 | case ComponentDatatype.UNSIGNED_SHORT: 62 | return function ( 63 | dataView, 64 | byteOffset, 65 | numberOfComponents, 66 | componentTypeByteLength, 67 | result, 68 | ) { 69 | for (let i = 0; i < numberOfComponents; ++i) { 70 | result[i] = dataView.getUint16( 71 | byteOffset + i * componentTypeByteLength, 72 | true, 73 | ); 74 | } 75 | }; 76 | case ComponentDatatype.INT: 77 | return function ( 78 | dataView, 79 | byteOffset, 80 | numberOfComponents, 81 | componentTypeByteLength, 82 | result, 83 | ) { 84 | for (let i = 0; i < numberOfComponents; ++i) { 85 | result[i] = dataView.getInt32( 86 | byteOffset + i * componentTypeByteLength, 87 | true, 88 | ); 89 | } 90 | }; 91 | case ComponentDatatype.UNSIGNED_INT: 92 | return function ( 93 | dataView, 94 | byteOffset, 95 | numberOfComponents, 96 | componentTypeByteLength, 97 | result, 98 | ) { 99 | for (let i = 0; i < numberOfComponents; ++i) { 100 | result[i] = dataView.getUint32( 101 | byteOffset + i * componentTypeByteLength, 102 | true, 103 | ); 104 | } 105 | }; 106 | case ComponentDatatype.FLOAT: 107 | return function ( 108 | dataView, 109 | byteOffset, 110 | numberOfComponents, 111 | componentTypeByteLength, 112 | result, 113 | ) { 114 | for (let i = 0; i < numberOfComponents; ++i) { 115 | result[i] = dataView.getFloat32( 116 | byteOffset + i * componentTypeByteLength, 117 | true, 118 | ); 119 | } 120 | }; 121 | case ComponentDatatype.DOUBLE: 122 | return function ( 123 | dataView, 124 | byteOffset, 125 | numberOfComponents, 126 | componentTypeByteLength, 127 | result, 128 | ) { 129 | for (let i = 0; i < numberOfComponents; ++i) { 130 | result[i] = dataView.getFloat64( 131 | byteOffset + i * componentTypeByteLength, 132 | true, 133 | ); 134 | } 135 | }; 136 | } 137 | } 138 | 139 | /** 140 | * A callback function that logs messages. 141 | * @callback ComponentReader 142 | * 143 | * @param {DataView} dataView The data view to read from. 144 | * @param {number} byteOffset The byte offset applied when reading from the data view. 145 | * @param {number} numberOfComponents The number of components to read. 146 | * @param {number} componentTypeByteLength The byte length of each component. 147 | * @param {number} result An array storing the components that are read. 148 | * 149 | * @private 150 | */ 151 | -------------------------------------------------------------------------------- /lib/getImageExtension.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | 4 | const RuntimeError = Cesium.RuntimeError; 5 | 6 | module.exports = getImageExtension; 7 | 8 | /** 9 | * Get the image extension from a Buffer containing image data. 10 | * 11 | * @param {Buffer} data The image data. 12 | * @returns {string} The image extension. 13 | * 14 | * @private 15 | */ 16 | function getImageExtension(data) { 17 | const header = data.slice(0, 2); 18 | const webpHeaderRIFFChars = data.slice(0, 4); 19 | const webpHeaderWEBPChars = data.slice(8, 12); 20 | 21 | if (header.equals(Buffer.from([0x42, 0x4d]))) { 22 | return ".bmp"; 23 | } else if (header.equals(Buffer.from([0x47, 0x49]))) { 24 | return ".gif"; 25 | } else if (header.equals(Buffer.from([0xff, 0xd8]))) { 26 | return ".jpg"; 27 | } else if (header.equals(Buffer.from([0x89, 0x50]))) { 28 | return ".png"; 29 | } else if (header.equals(Buffer.from([0xab, 0x4b]))) { 30 | return ".ktx2"; 31 | } else if (header.equals(Buffer.from([0x73, 0x42]))) { 32 | return ".basis"; 33 | } else if ( 34 | webpHeaderRIFFChars.equals(Buffer.from([0x52, 0x49, 0x46, 0x46])) && 35 | webpHeaderWEBPChars.equals(Buffer.from([0x57, 0x45, 0x42, 0x50])) 36 | ) { 37 | // See https://developers.google.com/speed/webp/docs/riff_container#webp_file_header 38 | return ".webp"; 39 | } 40 | 41 | throw new RuntimeError("Image data does not have valid header"); 42 | } 43 | -------------------------------------------------------------------------------- /lib/getJsonBufferPadded.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | 4 | const defaultValue = Cesium.defaultValue; 5 | 6 | module.exports = getJsonBufferPadded; 7 | 8 | /** 9 | * Convert the JSON object to a padded buffer. 10 | * 11 | * Pad the JSON with extra whitespace to fit the next 8-byte boundary. This ensures proper alignment 12 | * for the section that follows. glTF only requires 4-byte alignment but some extensions like 13 | * EXT_structural_metadata require 8-byte alignment for some buffer views. 14 | * 15 | * @param {object} json The JSON object. 16 | * @param {number} [byteOffset=0] The byte offset on which the buffer starts. 17 | * @returns {Buffer} The padded JSON buffer. 18 | * 19 | * @private 20 | */ 21 | function getJsonBufferPadded(json, byteOffset) { 22 | byteOffset = defaultValue(byteOffset, 0); 23 | let string = JSON.stringify(json); 24 | 25 | const boundary = 8; 26 | const byteLength = Buffer.byteLength(string); 27 | const remainder = (byteOffset + byteLength) % boundary; 28 | const padding = remainder === 0 ? 0 : boundary - remainder; 29 | let whitespace = ""; 30 | for (let i = 0; i < padding; ++i) { 31 | whitespace += " "; 32 | } 33 | string += whitespace; 34 | 35 | return Buffer.from(string); 36 | } 37 | -------------------------------------------------------------------------------- /lib/getStatistics.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const ForEach = require("./ForEach"); 4 | 5 | const defined = Cesium.defined; 6 | const isDataUri = Cesium.isDataUri; 7 | const WebGLConstants = Cesium.WebGLConstants; 8 | 9 | module.exports = getStatistics; 10 | 11 | /** 12 | * Returns an object containing the statistics for the glTF asset. 13 | * 14 | * @param {object} gltf A javascript object containing a glTF asset. 15 | * @param {number} [nodeId] If defined, statistics will only process number of draw calls and rendered primitives for the specified node. 16 | * @returns {Statistics} Object containing the statistics of the glTF asset. 17 | * 18 | * @see Statistics 19 | */ 20 | function getStatistics(gltf, nodeId) { 21 | const statistics = new Statistics(); 22 | 23 | if (defined(nodeId)) { 24 | const nodeDrawStats = getDrawCallStatisticsForNode(gltf, nodeId); 25 | statistics.numberOfDrawCalls = nodeDrawStats.numberOfDrawCalls; 26 | statistics.numberOfRenderedPrimitives = 27 | nodeDrawStats.numberOfRenderedPrimitives; 28 | return statistics; 29 | } 30 | 31 | const drawStats = getDrawCallStatistics(gltf); 32 | 33 | statistics.buffersByteLength = getBuffersByteLength(gltf); 34 | statistics.numberOfImages = defined(gltf.images) ? gltf.images.length : 0; 35 | statistics.numberOfExternalRequests = getNumberOfExternalRequests(gltf); 36 | statistics.numberOfDrawCalls = drawStats.numberOfDrawCalls; 37 | statistics.numberOfRenderedPrimitives = drawStats.numberOfRenderedPrimitives; 38 | statistics.numberOfNodes = defined(gltf.nodes) ? gltf.nodes.length : 0; 39 | statistics.numberOfMeshes = defined(gltf.meshes) ? gltf.meshes.length : 0; 40 | statistics.numberOfMaterials = defined(gltf.materials) 41 | ? gltf.materials.length 42 | : 0; 43 | statistics.numberOfAnimations = defined(gltf.animations) 44 | ? gltf.animations.length 45 | : 0; 46 | 47 | return statistics; 48 | } 49 | 50 | function getBuffersByteLength(gltf) { 51 | let byteLength = 0; 52 | ForEach.buffer(gltf, function (buffer) { 53 | byteLength += buffer.byteLength; 54 | }); 55 | return byteLength; 56 | } 57 | 58 | function getNumberOfExternalRequests(gltf) { 59 | let count = 0; 60 | ForEach.buffer(gltf, function (buffer) { 61 | if (defined(buffer.uri) && !isDataUri(buffer.uri)) { 62 | count++; 63 | } 64 | }); 65 | ForEach.image(gltf, function (image) { 66 | if (defined(image.uri) && !isDataUri(image.uri)) { 67 | count++; 68 | } 69 | }); 70 | ForEach.shader(gltf, function (shader) { 71 | if (defined(shader.uri) && !isDataUri(shader.uri)) { 72 | count++; 73 | } 74 | }); 75 | return count; 76 | } 77 | 78 | function getNumberOfRenderedPrimitives(gltf, primitive) { 79 | let count = 0; 80 | if (defined(primitive.indices)) { 81 | count = gltf.accessors[primitive.indices].count; 82 | } else if (defined(primitive.attributes.POSITION)) { 83 | count = gltf.accessors[primitive.attributes.POSITION].count; 84 | } 85 | switch (primitive.mode) { 86 | case WebGLConstants.POINTS: 87 | return count; 88 | case WebGLConstants.LINES: 89 | return count / 2; 90 | case WebGLConstants.LINE_LOOP: 91 | return count; 92 | case WebGLConstants.LINE_STRIP: 93 | return Math.max(count - 1, 0); 94 | case WebGLConstants.TRIANGLES: 95 | return count / 3; 96 | case WebGLConstants.TRIANGLE_STRIP: 97 | case WebGLConstants.TRIANGLE_FAN: 98 | return Math.max(count - 2, 0); 99 | default: 100 | // TRIANGLES 101 | return count / 3; 102 | } 103 | } 104 | 105 | function getDrawCallStatisticsForNode(gltf, nodeId) { 106 | let numberOfDrawCalls = 0; 107 | let numberOfRenderedPrimitives = 0; 108 | 109 | ForEach.nodeInTree(gltf, [nodeId], function (node) { 110 | const mesh = gltf.meshes[node.mesh]; 111 | if (defined(mesh)) { 112 | ForEach.meshPrimitive(mesh, function (primitive) { 113 | numberOfDrawCalls++; 114 | numberOfRenderedPrimitives += getNumberOfRenderedPrimitives( 115 | gltf, 116 | primitive, 117 | ); 118 | }); 119 | } 120 | }); 121 | 122 | return { 123 | numberOfDrawCalls: numberOfDrawCalls, 124 | numberOfRenderedPrimitives: numberOfRenderedPrimitives, 125 | }; 126 | } 127 | 128 | function getDrawCallStatistics(gltf) { 129 | let numberOfDrawCalls = 0; 130 | let numberOfRenderedPrimitives = 0; 131 | 132 | ForEach.mesh(gltf, function (mesh) { 133 | ForEach.meshPrimitive(mesh, function (primitive) { 134 | numberOfDrawCalls++; 135 | numberOfRenderedPrimitives += getNumberOfRenderedPrimitives( 136 | gltf, 137 | primitive, 138 | ); 139 | }); 140 | }); 141 | 142 | return { 143 | numberOfDrawCalls: numberOfDrawCalls, 144 | numberOfRenderedPrimitives: numberOfRenderedPrimitives, 145 | }; 146 | } 147 | 148 | /** 149 | * Contains statistics for a glTF asset. 150 | * 151 | * @property {number} buffersByteLength The total byte length of all buffers. 152 | * @property {number} numberOfImages The number of images in the asset. 153 | * @property {number} numberOfExternalRequests The number of external requests required to fetch the asset data. 154 | * @property {number} numberOfDrawCalls The number of draw calls required to render the asset. 155 | * @property {number} numberOfRenderedPrimitives The total number of rendered primitives in the asset (e.g. triangles). 156 | * @property {number} numberOfNodes The total number of nodes in the asset. 157 | * @property {number} numberOfMeshes The total number of meshes in the asset. 158 | * @property {number} numberOfMaterials The total number of materials in the asset. 159 | * @property {number} numberOfAnimations The total number of animations in the asset. 160 | * 161 | * @constructor 162 | * 163 | * @see getStatistics 164 | */ 165 | function Statistics() { 166 | this.buffersByteLength = 0; 167 | this.numberOfImages = 0; 168 | this.numberOfExternalRequests = 0; 169 | this.numberOfDrawCalls = 0; 170 | this.numberOfRenderedPrimitives = 0; 171 | this.numberOfNodes = 0; 172 | this.numberOfMeshes = 0; 173 | this.numberOfMaterials = 0; 174 | this.numberOfAnimations = 0; 175 | } 176 | 177 | /** 178 | * Creates a string listing the statistics along with their descriptions. 179 | * 180 | * @returns {string} A string describing the statistics for the glTF asset. 181 | */ 182 | Statistics.prototype.toString = function () { 183 | return ( 184 | `Total byte length of all buffers: ${this.buffersByteLength} bytes` + 185 | `\nImages: ${this.numberOfImages}\nDraw calls: ${this.numberOfDrawCalls}\nRendered primitives (e.g., triangles): ${this.numberOfRenderedPrimitives}\nNodes: ${this.numberOfNodes}\nMeshes: ${this.numberOfMeshes}\nMaterials: ${this.numberOfMaterials}\nAnimations: ${this.numberOfAnimations}\nExternal requests (not data uris): ${this.numberOfExternalRequests}` 186 | ); 187 | }; 188 | -------------------------------------------------------------------------------- /lib/glbToGltf.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const parseGlb = require("./parseGlb"); 3 | const processGltf = require("./processGltf"); 4 | 5 | module.exports = glbToGltf; 6 | 7 | /** 8 | * Convert a glb to glTF. 9 | * 10 | * @param {Buffer} glb A buffer containing the glb contents. 11 | * @param {object} [options] The same options object as {@link processGltf} 12 | * @returns {Promise} A promise that resolves to an object containing the glTF and a dictionary containing separate resources. 13 | */ 14 | function glbToGltf(glb, options) { 15 | const gltf = parseGlb(glb); 16 | return processGltf(gltf, options); 17 | } 18 | -------------------------------------------------------------------------------- /lib/gltfToGlb.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const getJsonBufferPadded = require("./getJsonBufferPadded"); 4 | const processGltf = require("./processGltf"); 5 | 6 | const clone = Cesium.clone; 7 | const defined = Cesium.defined; 8 | 9 | module.exports = gltfToGlb; 10 | 11 | /** 12 | * Convert a glTF to glb. 13 | * 14 | * @param {object} gltf A javascript object containing a glTF asset. 15 | * @param {object} [options] The same options object as {@link processGltf} 16 | * @returns {Promise} A promise that resolves to an object containing the glb and a dictionary containing separate resources. 17 | */ 18 | function gltfToGlb(gltf, options) { 19 | options = defined(options) ? clone(options) : {}; 20 | options.forceMergeBuffers = !options.separate; 21 | options.bufferStorage = { 22 | buffer: undefined, 23 | }; 24 | 25 | return processGltf(gltf, options).then(function (results) { 26 | return { 27 | glb: getGlb(results.gltf, options.bufferStorage.buffer), 28 | separateResources: results.separateResources, 29 | }; 30 | }); 31 | } 32 | 33 | function getGlb(gltf, binaryBuffer) { 34 | const jsonBuffer = getJsonBufferPadded(gltf, 20); 35 | 36 | // Compute glb length: (Global header) + (JSON chunk header) + (JSON chunk) + [(Binary chunk header) + (Binary chunk)] 37 | let glbLength = 12 + 8 + jsonBuffer.length; 38 | 39 | if (defined(binaryBuffer)) { 40 | glbLength += 8 + binaryBuffer.length; 41 | } 42 | 43 | const glb = Buffer.alloc(glbLength); 44 | 45 | // Write binary glTF header (magic, version, length) 46 | let byteOffset = 0; 47 | glb.writeUInt32LE(0x46546c67, byteOffset); 48 | byteOffset += 4; 49 | glb.writeUInt32LE(2, byteOffset); 50 | byteOffset += 4; 51 | glb.writeUInt32LE(glbLength, byteOffset); 52 | byteOffset += 4; 53 | 54 | // Write JSON Chunk header (length, type) 55 | glb.writeUInt32LE(jsonBuffer.length, byteOffset); 56 | byteOffset += 4; 57 | glb.writeUInt32LE(0x4e4f534a, byteOffset); // JSON 58 | byteOffset += 4; 59 | 60 | // Write JSON Chunk 61 | jsonBuffer.copy(glb, byteOffset); 62 | byteOffset += jsonBuffer.length; 63 | 64 | if (defined(binaryBuffer)) { 65 | // Write Binary Chunk header (length, type) 66 | glb.writeUInt32LE(binaryBuffer.length, byteOffset); 67 | byteOffset += 4; 68 | glb.writeUInt32LE(0x004e4942, byteOffset); // BIN 69 | byteOffset += 4; 70 | 71 | // Write Binary Chunk 72 | binaryBuffer.copy(glb, byteOffset); 73 | } 74 | 75 | return glb; 76 | } 77 | -------------------------------------------------------------------------------- /lib/mergeBuffers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const FS_WRITE_MAX_LENGTH = 2147479552; // See https://github.com/nodejs/node/issues/35605 3 | const BUFFER_MAX_LENGTH = require("buffer").constants.MAX_LENGTH; 4 | const BUFFER_MAX_BYTE_LENGTH = Math.min(FS_WRITE_MAX_LENGTH, BUFFER_MAX_LENGTH); 5 | const Cesium = require("cesium"); 6 | const ForEach = require("./ForEach"); 7 | 8 | const defaultValue = Cesium.defaultValue; 9 | const defined = Cesium.defined; 10 | 11 | module.exports = mergeBuffers; 12 | 13 | /** 14 | * Merge all buffers. Buffers with the same extras._pipeline.mergedBufferName will be merged together. 15 | * 16 | * @param {object} gltf A javascript object containing a glTF asset. 17 | * @param {string} [defaultName] The default name of the buffer data files. 18 | * @param {boolean} [force=false] Whether to force merging all buffers. 19 | * @returns {object} The glTF asset with its buffers merged. 20 | * 21 | * @private 22 | */ 23 | function mergeBuffers(gltf, defaultName, force) { 24 | let baseBufferName = defaultName; 25 | if (!defined(baseBufferName)) { 26 | ForEach.buffer(gltf, function (buffer) { 27 | baseBufferName = defaultValue(baseBufferName, buffer.name); 28 | }); 29 | baseBufferName = defaultValue(baseBufferName, "buffer"); 30 | } 31 | 32 | let buffersByteLength = 0; 33 | const emptyBuffers = []; 34 | const emptyBufferIds = []; 35 | 36 | ForEach.buffer(gltf, function (buffer, bufferId) { 37 | const source = buffer.extras._pipeline.source; 38 | if (defined(source)) { 39 | buffersByteLength += source.length; 40 | } else { 41 | emptyBuffers.push(buffer); 42 | emptyBufferIds.push(bufferId); 43 | } 44 | 45 | const extensions = defaultValue( 46 | buffer.extensions, 47 | defaultValue.EMPTY_OBJECT, 48 | ); 49 | const meshoptObject = extensions.EXT_meshopt_compression; 50 | if (defined(meshoptObject) && meshoptObject.fallback) { 51 | // Prevent empty meshopt buffer from being merged into main buffer 52 | buffer.extras._pipeline.mergedBufferName = `meshopt-fallback-${bufferId}`; 53 | } 54 | }); 55 | 56 | // Don't merge buffers if the merged buffer will exceed the Node limit. 57 | const splitBuffers = 58 | buffersByteLength > mergeBuffers._getBufferMaxByteLength(); 59 | 60 | const buffersToMerge = {}; 61 | const mergedNameCount = {}; 62 | 63 | forEachBufferViewLikeObject(gltf, function (bufferView) { 64 | const buffer = gltf.buffers[bufferView.buffer]; 65 | const source = buffer.extras._pipeline.source; 66 | if (!defined(source)) { 67 | return; 68 | } 69 | 70 | let mergedName = buffer.extras._pipeline.mergedBufferName; 71 | mergedName = defined(mergedName) 72 | ? `${baseBufferName}-${mergedName}` 73 | : baseBufferName; 74 | 75 | if (splitBuffers) { 76 | if (!defined(mergedNameCount[mergedName])) { 77 | mergedNameCount[mergedName] = 0; 78 | } 79 | mergedName += `-${mergedNameCount[mergedName]++}`; 80 | } 81 | 82 | if (force) { 83 | mergedName = baseBufferName; 84 | } 85 | 86 | if (!defined(buffersToMerge[mergedName])) { 87 | buffersToMerge[mergedName] = { 88 | buffers: [], 89 | byteLength: 0, 90 | index: Object.keys(buffersToMerge).length, 91 | }; 92 | } 93 | const buffers = buffersToMerge[mergedName].buffers; 94 | let byteLength = buffersToMerge[mergedName].byteLength; 95 | const index = buffersToMerge[mergedName].index; 96 | 97 | const sourceBufferViewData = Buffer.from( 98 | source.slice( 99 | bufferView.byteOffset, 100 | bufferView.byteOffset + bufferView.byteLength, 101 | ), 102 | ); 103 | const bufferViewPadding = allocateBufferPadding(byteLength); 104 | if (defined(bufferViewPadding)) { 105 | buffers.push(bufferViewPadding); 106 | byteLength += bufferViewPadding.byteLength; 107 | } 108 | 109 | bufferView.byteOffset = byteLength; 110 | bufferView.buffer = index; 111 | 112 | buffers.push(sourceBufferViewData); 113 | byteLength += sourceBufferViewData.byteLength; 114 | 115 | buffersToMerge[mergedName].byteLength = byteLength; 116 | }); 117 | 118 | const buffersLength = Object.keys(buffersToMerge).length; 119 | gltf.buffers = new Array(buffersLength); 120 | 121 | for (const mergedName in buffersToMerge) { 122 | if (Object.prototype.hasOwnProperty.call(buffersToMerge, mergedName)) { 123 | const buffers = buffersToMerge[mergedName].buffers; 124 | const byteLength = buffersToMerge[mergedName].byteLength; 125 | const index = buffersToMerge[mergedName].index; 126 | const bufferPadding = allocateBufferPadding(byteLength); 127 | if (defined(bufferPadding)) { 128 | buffers.push(bufferPadding); 129 | } 130 | const mergedSource = 131 | buffers.length > 1 ? Buffer.concat(buffers) : buffers[0]; 132 | gltf.buffers[index] = { 133 | name: mergedName, 134 | byteLength: mergedSource.byteLength, 135 | extras: { 136 | _pipeline: { 137 | source: mergedSource, 138 | }, 139 | }, 140 | }; 141 | } 142 | } 143 | 144 | const emptyBuffersLength = emptyBuffers.length; 145 | for (let i = 0; i < emptyBuffersLength; ++i) { 146 | const emptyBuffer = emptyBuffers[i]; 147 | const emptyBufferId = emptyBufferIds[i]; 148 | const newBufferId = gltf.buffers.length; 149 | forEachBufferViewLikeObject(gltf, function (bufferView) { 150 | if (bufferView.buffer === emptyBufferId) { 151 | bufferView.buffer = newBufferId; 152 | } 153 | }); 154 | gltf.buffers.push(emptyBuffer); 155 | } 156 | 157 | return gltf; 158 | } 159 | 160 | function forEachBufferViewLikeObject(gltf, callback) { 161 | ForEach.bufferView(gltf, function (bufferView) { 162 | callback(bufferView); 163 | 164 | const extensions = defaultValue( 165 | bufferView.extensions, 166 | defaultValue.EMPTY_OBJECT, 167 | ); 168 | const meshoptObject = extensions.EXT_meshopt_compression; 169 | if (defined(meshoptObject)) { 170 | // The EXT_meshopt_compression object has buffer, byteOffset, and byteLength properties like a bufferView 171 | callback(meshoptObject); 172 | } 173 | }); 174 | } 175 | 176 | function allocateBufferPadding(byteLength) { 177 | const boundary = 8; 178 | const remainder = byteLength % boundary; 179 | const padding = remainder === 0 ? 0 : boundary - remainder; 180 | if (padding > 0) { 181 | return Buffer.alloc(padding); 182 | } 183 | return undefined; 184 | } 185 | 186 | // Exposed for testing 187 | mergeBuffers._getBufferMaxByteLength = function () { 188 | return BUFFER_MAX_BYTE_LENGTH; 189 | }; 190 | -------------------------------------------------------------------------------- /lib/moveTechniqueRenderStates.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Cesium = require("cesium"); 4 | const addExtensionsUsed = require("./addExtensionsUsed"); 5 | const ForEach = require("./ForEach"); 6 | 7 | const defaultValue = Cesium.defaultValue; 8 | const defined = Cesium.defined; 9 | const WebGLConstants = Cesium.WebGLConstants; 10 | 11 | module.exports = moveTechniqueRenderStates; 12 | 13 | const defaultBlendEquation = [WebGLConstants.FUNC_ADD, WebGLConstants.FUNC_ADD]; 14 | 15 | const defaultBlendFactors = [ 16 | WebGLConstants.ONE, 17 | WebGLConstants.ZERO, 18 | WebGLConstants.ONE, 19 | WebGLConstants.ZERO, 20 | ]; 21 | 22 | function isStateEnabled(renderStates, state) { 23 | const enabled = renderStates.enable; 24 | if (!defined(enabled)) { 25 | return false; 26 | } 27 | 28 | return enabled.indexOf(state) > -1; 29 | } 30 | 31 | const supportedBlendFactors = [ 32 | WebGLConstants.ZERO, 33 | WebGLConstants.ONE, 34 | WebGLConstants.SRC_COLOR, 35 | WebGLConstants.ONE_MINUS_SRC_COLOR, 36 | WebGLConstants.SRC_ALPHA, 37 | WebGLConstants.ONE_MINUS_SRC_ALPHA, 38 | WebGLConstants.DST_ALPHA, 39 | WebGLConstants.ONE_MINUS_DST_ALPHA, 40 | WebGLConstants.DST_COLOR, 41 | WebGLConstants.ONE_MINUS_DST_COLOR, 42 | ]; 43 | 44 | // If any of the blend factors are not supported, return the default 45 | function getSupportedBlendFactors(value, defaultValue) { 46 | if (!defined(value)) { 47 | return defaultValue; 48 | } 49 | 50 | for (let i = 0; i < 4; i++) { 51 | if (supportedBlendFactors.indexOf(value[i]) === -1) { 52 | return defaultValue; 53 | } 54 | } 55 | 56 | return value; 57 | } 58 | 59 | /** 60 | * Move glTF 1.0 technique render states to glTF 2.0 materials properties and KHR_blend extension. 61 | * 62 | * @param {object} gltf A javascript object containing a glTF asset. 63 | * @returns {object} The updated glTF asset. 64 | * 65 | * @private 66 | */ 67 | function moveTechniqueRenderStates(gltf) { 68 | const blendingForTechnique = {}; 69 | const materialPropertiesForTechnique = {}; 70 | const techniquesLegacy = gltf.techniques; 71 | if (!defined(techniquesLegacy)) { 72 | return gltf; 73 | } 74 | 75 | ForEach.technique(gltf, function (techniqueLegacy, techniqueIndex) { 76 | const renderStates = techniqueLegacy.states; 77 | if (defined(renderStates)) { 78 | const materialProperties = (materialPropertiesForTechnique[ 79 | techniqueIndex 80 | ] = {}); 81 | 82 | // If BLEND is enabled, the material should have alpha mode BLEND 83 | if (isStateEnabled(renderStates, WebGLConstants.BLEND)) { 84 | materialProperties.alphaMode = "BLEND"; 85 | 86 | const blendFunctions = renderStates.functions; 87 | if ( 88 | defined(blendFunctions) && 89 | (defined(blendFunctions.blendEquationSeparate) || 90 | defined(blendFunctions.blendFuncSeparate)) 91 | ) { 92 | blendingForTechnique[techniqueIndex] = { 93 | blendEquation: defaultValue( 94 | blendFunctions.blendEquationSeparate, 95 | defaultBlendEquation, 96 | ), 97 | blendFactors: getSupportedBlendFactors( 98 | blendFunctions.blendFuncSeparate, 99 | defaultBlendFactors, 100 | ), 101 | }; 102 | } 103 | } 104 | 105 | // If CULL_FACE is not enabled, the material should be doubleSided 106 | if (!isStateEnabled(renderStates, WebGLConstants.CULL_FACE)) { 107 | materialProperties.doubleSided = true; 108 | } 109 | 110 | delete techniqueLegacy.states; 111 | } 112 | }); 113 | 114 | if (Object.keys(blendingForTechnique).length > 0) { 115 | if (!defined(gltf.extensions)) { 116 | gltf.extensions = {}; 117 | } 118 | 119 | addExtensionsUsed(gltf, "KHR_blend"); 120 | } 121 | 122 | ForEach.material(gltf, function (material) { 123 | if (defined(material.technique)) { 124 | const materialProperties = 125 | materialPropertiesForTechnique[material.technique]; 126 | ForEach.objectLegacy(materialProperties, function (value, property) { 127 | material[property] = value; 128 | }); 129 | 130 | const blending = blendingForTechnique[material.technique]; 131 | if (defined(blending)) { 132 | if (!defined(material.extensions)) { 133 | material.extensions = {}; 134 | } 135 | 136 | material.extensions.KHR_blend = blending; 137 | } 138 | } 139 | }); 140 | 141 | return gltf; 142 | } 143 | -------------------------------------------------------------------------------- /lib/moveTechniquesToExtension.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Cesium = require("cesium"); 4 | const addExtensionsUsed = require("./addExtensionsUsed"); 5 | const addExtensionsRequired = require("./addExtensionsRequired"); 6 | const addToArray = require("./addToArray"); 7 | const ForEach = require("./ForEach"); 8 | 9 | const defined = Cesium.defined; 10 | 11 | module.exports = moveTechniquesToExtension; 12 | 13 | /** 14 | * Move glTF 1.0 material techniques to glTF 2.0 KHR_techniques_webgl extension. 15 | * 16 | * @param {object} gltf A javascript object containing a glTF asset. 17 | * @returns {object} The updated glTF asset. 18 | * 19 | * @private 20 | */ 21 | function moveTechniquesToExtension(gltf) { 22 | const techniquesLegacy = gltf.techniques; 23 | const mappedUniforms = {}; 24 | const updatedTechniqueIndices = {}; 25 | const seenPrograms = {}; 26 | if (defined(techniquesLegacy)) { 27 | const extension = { 28 | programs: [], 29 | shaders: [], 30 | techniques: [], 31 | }; 32 | 33 | // Some 1.1 models have a glExtensionsUsed property that can be transferred to program.glExtensions 34 | const glExtensions = gltf.glExtensionsUsed; 35 | delete gltf.glExtensionsUsed; 36 | 37 | ForEach.technique(gltf, function (techniqueLegacy, techniqueId) { 38 | const technique = { 39 | name: techniqueLegacy.name, 40 | program: undefined, 41 | attributes: {}, 42 | uniforms: {}, 43 | }; 44 | 45 | let parameterLegacy; 46 | ForEach.techniqueAttribute( 47 | techniqueLegacy, 48 | function (parameterName, attributeName) { 49 | parameterLegacy = techniqueLegacy.parameters[parameterName]; 50 | technique.attributes[attributeName] = { 51 | semantic: parameterLegacy.semantic, 52 | }; 53 | }, 54 | ); 55 | 56 | ForEach.techniqueUniform( 57 | techniqueLegacy, 58 | function (parameterName, uniformName) { 59 | parameterLegacy = techniqueLegacy.parameters[parameterName]; 60 | technique.uniforms[uniformName] = { 61 | count: parameterLegacy.count, 62 | node: parameterLegacy.node, 63 | type: parameterLegacy.type, 64 | semantic: parameterLegacy.semantic, 65 | value: parameterLegacy.value, 66 | }; 67 | 68 | // Store the name of the uniform to update material values. 69 | if (!defined(mappedUniforms[techniqueId])) { 70 | mappedUniforms[techniqueId] = {}; 71 | } 72 | mappedUniforms[techniqueId][parameterName] = uniformName; 73 | }, 74 | ); 75 | 76 | if (!defined(seenPrograms[techniqueLegacy.program])) { 77 | const programLegacy = gltf.programs[techniqueLegacy.program]; 78 | 79 | const program = { 80 | name: programLegacy.name, 81 | fragmentShader: undefined, 82 | vertexShader: undefined, 83 | glExtensions: glExtensions, 84 | }; 85 | 86 | const fs = gltf.shaders[programLegacy.fragmentShader]; 87 | program.fragmentShader = addToArray(extension.shaders, fs, true); 88 | 89 | const vs = gltf.shaders[programLegacy.vertexShader]; 90 | program.vertexShader = addToArray(extension.shaders, vs, true); 91 | 92 | technique.program = addToArray(extension.programs, program); 93 | seenPrograms[techniqueLegacy.program] = technique.program; 94 | } else { 95 | technique.program = seenPrograms[techniqueLegacy.program]; 96 | } 97 | 98 | // Store the index of the new technique to reference instead. 99 | updatedTechniqueIndices[techniqueId] = addToArray( 100 | extension.techniques, 101 | technique, 102 | ); 103 | }); 104 | 105 | if (extension.techniques.length > 0) { 106 | if (!defined(gltf.extensions)) { 107 | gltf.extensions = {}; 108 | } 109 | 110 | gltf.extensions.KHR_techniques_webgl = extension; 111 | addExtensionsUsed(gltf, "KHR_techniques_webgl"); 112 | addExtensionsRequired(gltf, "KHR_techniques_webgl"); 113 | } 114 | } 115 | 116 | ForEach.material(gltf, function (material) { 117 | if (defined(material.technique)) { 118 | const materialExtension = { 119 | technique: updatedTechniqueIndices[material.technique], 120 | }; 121 | 122 | ForEach.objectLegacy(material.values, function (value, parameterName) { 123 | if (!defined(materialExtension.values)) { 124 | materialExtension.values = {}; 125 | } 126 | 127 | const uniformName = mappedUniforms[material.technique][parameterName]; 128 | if (defined(uniformName)) { 129 | materialExtension.values[uniformName] = value; 130 | } 131 | }); 132 | 133 | if (!defined(material.extensions)) { 134 | material.extensions = {}; 135 | } 136 | 137 | material.extensions.KHR_techniques_webgl = materialExtension; 138 | } 139 | 140 | delete material.technique; 141 | delete material.values; 142 | }); 143 | 144 | delete gltf.techniques; 145 | delete gltf.programs; 146 | delete gltf.shaders; 147 | 148 | return gltf; 149 | } 150 | -------------------------------------------------------------------------------- /lib/numberOfComponentsForType.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = numberOfComponentsForType; 4 | 5 | /** 6 | * Utility function for retrieving the number of components in a given type. 7 | * 8 | * @param {string} type glTF type 9 | * @returns {number} The number of components in that type. 10 | * 11 | * @private 12 | */ 13 | function numberOfComponentsForType(type) { 14 | switch (type) { 15 | case "SCALAR": 16 | return 1; 17 | case "VEC2": 18 | return 2; 19 | case "VEC3": 20 | return 3; 21 | case "VEC4": 22 | case "MAT2": 23 | return 4; 24 | case "MAT3": 25 | return 9; 26 | case "MAT4": 27 | return 16; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/parseGlb.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const addPipelineExtras = require("./addPipelineExtras"); 4 | const removeExtensionsUsed = require("./removeExtensionsUsed"); 5 | 6 | const defaultValue = Cesium.defaultValue; 7 | const defined = Cesium.defined; 8 | const getMagic = Cesium.getMagic; 9 | const getStringFromTypedArray = Cesium.getStringFromTypedArray; 10 | const RuntimeError = Cesium.RuntimeError; 11 | 12 | const sizeOfUint32 = 4; 13 | 14 | module.exports = parseGlb; 15 | 16 | /** 17 | * Convert a binary glTF to glTF. 18 | * 19 | * The returned glTF has pipeline extras included. The embedded binary data is stored in gltf.buffers[0].extras._pipeline.source. 20 | * 21 | * @param {Buffer} glb The glb data to parse. 22 | * @returns {object} A javascript object containing a glTF asset with pipeline extras included. 23 | * 24 | * @private 25 | */ 26 | function parseGlb(glb) { 27 | // Check that the magic string is present 28 | const magic = getMagic(glb); 29 | if (magic !== "glTF") { 30 | throw new RuntimeError("File is not valid binary glTF"); 31 | } 32 | 33 | const header = readHeader(glb, 0, 5); 34 | const version = header[1]; 35 | if (version !== 1 && version !== 2) { 36 | throw new RuntimeError("Binary glTF version is not 1 or 2"); 37 | } 38 | 39 | if (version === 1) { 40 | return parseGlbVersion1(glb, header); 41 | } 42 | 43 | return parseGlbVersion2(glb, header); 44 | } 45 | 46 | function readHeader(glb, byteOffset, count) { 47 | const dataView = new DataView(glb.buffer); 48 | const header = new Array(count); 49 | for (let i = 0; i < count; ++i) { 50 | header[i] = dataView.getUint32( 51 | glb.byteOffset + byteOffset + i * sizeOfUint32, 52 | true, 53 | ); 54 | } 55 | return header; 56 | } 57 | 58 | function parseGlbVersion1(glb, header) { 59 | const length = header[2]; 60 | const contentLength = header[3]; 61 | const contentFormat = header[4]; 62 | 63 | // Check that the content format is 0, indicating that it is JSON 64 | if (contentFormat !== 0) { 65 | throw new RuntimeError("Binary glTF scene format is not JSON"); 66 | } 67 | 68 | const jsonStart = 20; 69 | const binaryStart = jsonStart + contentLength; 70 | 71 | const contentString = getStringFromTypedArray(glb, jsonStart, contentLength); 72 | const gltf = JSON.parse(contentString); 73 | addPipelineExtras(gltf); 74 | 75 | const binaryBuffer = glb.subarray(binaryStart, length); 76 | 77 | const buffers = gltf.buffers; 78 | if (defined(buffers) && Object.keys(buffers).length > 0) { 79 | // In some older models, the binary glTF buffer is named KHR_binary_glTF 80 | const binaryGltfBuffer = defaultValue( 81 | buffers.binary_glTF, 82 | buffers.KHR_binary_glTF, 83 | ); 84 | if (defined(binaryGltfBuffer)) { 85 | binaryGltfBuffer.extras._pipeline.source = binaryBuffer; 86 | delete binaryGltfBuffer.uri; 87 | } 88 | } 89 | // Remove the KHR_binary_glTF extension 90 | removeExtensionsUsed(gltf, "KHR_binary_glTF"); 91 | return gltf; 92 | } 93 | 94 | function parseGlbVersion2(glb, header) { 95 | const length = header[2]; 96 | let byteOffset = 12; 97 | let gltf; 98 | let binaryBuffer; 99 | while (byteOffset < length) { 100 | const chunkHeader = readHeader(glb, byteOffset, 2); 101 | const chunkLength = chunkHeader[0]; 102 | const chunkType = chunkHeader[1]; 103 | byteOffset += 8; 104 | const chunkBuffer = glb.subarray(byteOffset, byteOffset + chunkLength); 105 | byteOffset += chunkLength; 106 | // Load JSON chunk 107 | if (chunkType === 0x4e4f534a) { 108 | const jsonString = getStringFromTypedArray(chunkBuffer); 109 | gltf = JSON.parse(jsonString); 110 | addPipelineExtras(gltf); 111 | } 112 | // Load Binary chunk 113 | else if (chunkType === 0x004e4942) { 114 | binaryBuffer = chunkBuffer; 115 | } 116 | } 117 | if (defined(gltf) && defined(binaryBuffer)) { 118 | const buffers = gltf.buffers; 119 | if (defined(buffers) && buffers.length > 0) { 120 | const buffer = buffers[0]; 121 | buffer.extras._pipeline.source = binaryBuffer; 122 | } 123 | } 124 | return gltf; 125 | } 126 | -------------------------------------------------------------------------------- /lib/processGlb.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const parseGlb = require("./parseGlb"); 3 | const gltfToGlb = require("./gltfToGlb"); 4 | 5 | module.exports = processGlb; 6 | 7 | /** 8 | * Run a glb through the gltf-pipeline. 9 | * 10 | * @param {Buffer} glb A buffer containing the glb contents. 11 | * @param {object} [options] The same options object as {@link processGltf} 12 | * @returns {Promise} A promise that resolves to an object containing the glb and a dictionary containing separate resources. 13 | */ 14 | function processGlb(glb, options) { 15 | const gltf = parseGlb(glb); 16 | return gltfToGlb(gltf, options); 17 | } 18 | -------------------------------------------------------------------------------- /lib/processGltf.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const Promise = require("bluebird"); 4 | const addDefaults = require("./addDefaults"); 5 | const addPipelineExtras = require("./addPipelineExtras"); 6 | const getStatistics = require("./getStatistics"); 7 | const readResources = require("./readResources"); 8 | const removeDefaults = require("./removeDefaults"); 9 | const removePipelineExtras = require("./removePipelineExtras"); 10 | const removeUnusedElements = require("./removeUnusedElements"); 11 | const updateVersion = require("./updateVersion"); 12 | const writeResources = require("./writeResources"); 13 | const compressDracoMeshes = require("./compressDracoMeshes"); 14 | 15 | const clone = Cesium.clone; 16 | const defaultValue = Cesium.defaultValue; 17 | const defined = Cesium.defined; 18 | 19 | module.exports = processGltf; 20 | 21 | /** 22 | * Run a glTF through the gltf-pipeline. 23 | * 24 | * @param {object} gltf A javascript object containing a glTF asset. The glTF is modified in place. 25 | * @param {object} [options] An object with the following properties: 26 | * @param {string} [options.resourceDirectory] The path for reading separate resources. 27 | * @param {string} [options.name] The name of the glTF asset, for writing separate resources. 28 | * @param {boolean} [options.separate = false] Write separate buffers, shaders, and textures instead of embedding them in the glTF. 29 | * @param {boolean} [options.separateTextures = false] Write out separate textures only. 30 | * @param {boolean} [options.stats = false] Print statistics to console for input and output glTF files. 31 | * @param {object} [options.dracoOptions] Options to pass to the compressDracoMeshes stage. If undefined, stage is not run. 32 | * @param {Stage[]} [options.customStages] Custom stages to run on the glTF asset. 33 | * @param {Logger} [options.logger] A callback function for handling logged messages. Defaults to console.log. 34 | * @param {string[]} [options.baseColorTextureNames] Names of uniforms that indicate base color textures. 35 | * @param {string[]} [options.baseColorFactorNames] Names of uniforms that indicate base color factors. 36 | * 37 | * @returns {Promise} A promise that resolves to an object containing the glTF and a dictionary containing separate resources. 38 | */ 39 | function processGltf(gltf, options) { 40 | const defaults = processGltf.defaults; 41 | options = defined(options) ? clone(options) : {}; 42 | options.separateBuffers = defaultValue(options.separate, defaults.separate); 43 | options.separateShaders = defaultValue(options.separate, defaults.separate); 44 | options.separateTextures = 45 | defaultValue(options.separateTextures, defaults.separateTextures) || 46 | options.separate; 47 | options.stats = defaultValue(options.stats, defaults.stats); 48 | options.logger = defaultValue(options.logger, getDefaultLogger()); 49 | options.separateResources = {}; 50 | options.customStages = defaultValue(options.customStages, []); 51 | 52 | const preStages = [ 53 | addPipelineExtras, 54 | readResources, 55 | updateVersion, 56 | addDefaults, 57 | ]; 58 | 59 | const postStages = [writeResources, removePipelineExtras, removeDefaults]; 60 | 61 | const pipelineStages = getStages(options); 62 | const stages = preStages.concat( 63 | options.customStages, 64 | pipelineStages, 65 | postStages, 66 | ); 67 | 68 | return Promise.each(stages, function (stage) { 69 | return stage(gltf, options); 70 | }).then(function () { 71 | printStats(gltf, options, true); 72 | return { 73 | gltf: gltf, 74 | separateResources: options.separateResources, 75 | }; 76 | }); 77 | } 78 | 79 | function printStats(gltf, options, processed) { 80 | if (options.stats) { 81 | options.logger(processed ? "Statistics after:" : "Statistics before:"); 82 | options.logger(getStatistics(gltf).toString()); 83 | } 84 | } 85 | 86 | function getStages(options) { 87 | const stages = []; 88 | if (defined(options.dracoOptions)) { 89 | stages.push(compressDracoMeshes); 90 | } 91 | if (!options.keepUnusedElements) { 92 | stages.push(function (gltf, options) { 93 | removeUnusedElements(gltf); 94 | }); 95 | } 96 | return stages; 97 | } 98 | 99 | function getDefaultLogger() { 100 | return function (message) { 101 | console.log(message); 102 | }; 103 | } 104 | 105 | /** 106 | * Default values that will be used when calling processGltf(options) unless specified in the options object. 107 | */ 108 | processGltf.defaults = { 109 | /** 110 | * Gets or sets whether to write out separate buffers, shaders, and textures instead of embedding them in the glTF 111 | * @type Boolean 112 | * @default false 113 | */ 114 | separate: false, 115 | /** 116 | * Gets or sets whether to write out separate textures only. 117 | * @type Boolean 118 | * @default false 119 | */ 120 | separateTextures: false, 121 | /** 122 | * Gets or sets whether to print statistics to console for input and output glTF files. 123 | * @type Boolean 124 | * @default false 125 | */ 126 | stats: false, 127 | /** 128 | * Keep unused 'node', 'mesh' and 'material' elements. 129 | * @type Boolean 130 | * @default false 131 | */ 132 | keepUnusedElements: false, 133 | /** 134 | * When false, materials with KHR_techniques_webgl, KHR_blend, or KHR_materials_common will be converted to PBR. 135 | * @type Boolean 136 | * @default false 137 | */ 138 | keepLegacyExtensions: false, 139 | /** 140 | * Gets or sets whether to compress the meshes using Draco. Adds the KHR_draco_mesh_compression extension. 141 | * @type Boolean 142 | * @default false 143 | */ 144 | compressDracoMeshes: false, 145 | }; 146 | 147 | /** 148 | * A callback function that logs messages. 149 | * @callback Logger 150 | * 151 | * @param {string} message The message to log. 152 | */ 153 | 154 | /** 155 | * A stage that processes a glTF asset. 156 | * @callback Stage 157 | * 158 | * @param {object} gltf The glTF asset. 159 | * @returns {Promise|Object} The glTF asset or a promise that resolves to the glTF asset. 160 | */ 161 | -------------------------------------------------------------------------------- /lib/readAccessorPacked.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const getAccessorByteStride = require("./getAccessorByteStride"); 4 | const getComponentReader = require("./getComponentReader"); 5 | const numberOfComponentsForType = require("./numberOfComponentsForType"); 6 | 7 | const ComponentDatatype = Cesium.ComponentDatatype; 8 | const defined = Cesium.defined; 9 | 10 | module.exports = readAccessorPacked; 11 | 12 | /** 13 | * Returns the accessor data in a contiguous array. 14 | * 15 | * @param {object} gltf A javascript object containing a glTF asset. 16 | * @param {object} accessor The accessor. 17 | * @returns {Array} The accessor values in a contiguous array. 18 | * 19 | * @private 20 | */ 21 | function readAccessorPacked(gltf, accessor) { 22 | const byteStride = getAccessorByteStride(gltf, accessor); 23 | const componentTypeByteLength = ComponentDatatype.getSizeInBytes( 24 | accessor.componentType, 25 | ); 26 | const numberOfComponents = numberOfComponentsForType(accessor.type); 27 | const count = accessor.count; 28 | const values = new Array(numberOfComponents * count); 29 | 30 | if (!defined(accessor.bufferView)) { 31 | return values.fill(0); 32 | } 33 | 34 | const bufferView = gltf.bufferViews[accessor.bufferView]; 35 | const source = gltf.buffers[bufferView.buffer].extras._pipeline.source; 36 | let byteOffset = 37 | accessor.byteOffset + bufferView.byteOffset + source.byteOffset; 38 | 39 | const dataView = new DataView(source.buffer); 40 | const components = new Array(numberOfComponents); 41 | const componentReader = getComponentReader(accessor.componentType); 42 | 43 | for (let i = 0; i < count; ++i) { 44 | componentReader( 45 | dataView, 46 | byteOffset, 47 | numberOfComponents, 48 | componentTypeByteLength, 49 | components, 50 | ); 51 | for (let j = 0; j < numberOfComponents; ++j) { 52 | values[i * numberOfComponents + j] = components[j]; 53 | } 54 | byteOffset += byteStride; 55 | } 56 | return values; 57 | } 58 | -------------------------------------------------------------------------------- /lib/readResources.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const fsExtra = require("fs-extra"); 4 | const path = require("path"); 5 | const Promise = require("bluebird"); 6 | const { URL } = require("url"); 7 | 8 | const addPipelineExtras = require("./addPipelineExtras"); 9 | const dataUriToBuffer = require("./dataUriToBuffer"); 10 | const { fileURLToPath, pathToFileURL } = require("./FileUrl"); 11 | const ForEach = require("./ForEach"); 12 | 13 | const defined = Cesium.defined; 14 | const defaultValue = Cesium.defaultValue; 15 | const isDataUri = Cesium.isDataUri; 16 | const RuntimeError = Cesium.RuntimeError; 17 | 18 | module.exports = readResources; 19 | 20 | /** 21 | * Read data uris, buffer views, or files referenced by the glTF into buffers. 22 | * The buffer data is placed into extras._pipeline.source for the corresponding object. 23 | * This stage runs before updateVersion and handles both glTF 1.0 and glTF 2.0 assets. 24 | * 25 | * @param {object} gltf A javascript object containing a glTF asset. 26 | * @param {object} [options] Object with the following properties: 27 | * @param {string} [options.resourceDirectory] The path to look in when reading separate files. 28 | * @returns {Promise} A promise that resolves to the glTF asset when all resources are read. 29 | * 30 | * @private 31 | */ 32 | function readResources(gltf, options) { 33 | addPipelineExtras(gltf); 34 | options = defaultValue(options, {}); 35 | 36 | // Make sure its an absolute path with a trailing separator 37 | options.resourceDirectory = defined(options.resourceDirectory) 38 | ? path.resolve(options.resourceDirectory) + path.sep 39 | : undefined; 40 | 41 | const bufferPromises = []; 42 | const resourcePromises = []; 43 | 44 | ForEach.buffer(gltf, function (buffer) { 45 | bufferPromises.push(readBuffer(gltf, buffer, options)); 46 | }); 47 | 48 | // Buffers need to be read first because images and shader may resolve to bufferViews 49 | return Promise.all(bufferPromises) 50 | .then(function () { 51 | ForEach.shader(gltf, function (shader) { 52 | resourcePromises.push(readShader(gltf, shader, options)); 53 | }); 54 | ForEach.image(gltf, function (image) { 55 | resourcePromises.push(readImage(gltf, image, options)); 56 | }); 57 | return Promise.all(resourcePromises); 58 | }) 59 | .then(function () { 60 | return gltf; 61 | }); 62 | } 63 | 64 | function readBuffer(gltf, buffer, options) { 65 | return readResource(gltf, buffer, false, options).then(function (data) { 66 | if (defined(data)) { 67 | buffer.extras._pipeline.source = data; 68 | } 69 | }); 70 | } 71 | 72 | function readImage(gltf, image, options) { 73 | return readResource(gltf, image, true, options).then(function (data) { 74 | image.extras._pipeline.source = data; 75 | }); 76 | } 77 | 78 | function readShader(gltf, shader, options) { 79 | return readResource(gltf, shader, true, options).then(function (data) { 80 | shader.extras._pipeline.source = data.toString(); 81 | }); 82 | } 83 | 84 | function readResource(gltf, object, saveResourceId, options) { 85 | const uri = object.uri; 86 | delete object.uri; // Don't hold onto the uri, its contents will be stored in extras._pipeline.source 87 | 88 | // Source already exists if the gltf was converted from a glb 89 | const source = object.extras._pipeline.source; 90 | if (defined(source)) { 91 | return Promise.resolve(Buffer.from(source)); 92 | } 93 | // Handle reading buffer view from 1.0 glb model 94 | const extensions = object.extensions; 95 | if (defined(extensions)) { 96 | const khrBinaryGltf = extensions.KHR_binary_glTF; 97 | if (defined(khrBinaryGltf)) { 98 | return Promise.resolve( 99 | readBufferView(gltf, khrBinaryGltf.bufferView, object, saveResourceId), 100 | ); 101 | } 102 | } 103 | if (defined(object.bufferView)) { 104 | return Promise.resolve( 105 | readBufferView(gltf, object.bufferView, object, saveResourceId), 106 | ); 107 | } 108 | if (!defined(uri)) { 109 | return Promise.resolve(undefined); 110 | } 111 | if (isDataUri(uri)) { 112 | return Promise.resolve(dataUriToBuffer(uri)); 113 | } 114 | return readFile(object, uri, saveResourceId, options); 115 | } 116 | 117 | function readBufferView(gltf, bufferViewId, object, saveResourceId) { 118 | if (saveResourceId) { 119 | object.extras._pipeline.resourceId = bufferViewId; 120 | } 121 | const bufferView = gltf.bufferViews[bufferViewId]; 122 | const buffer = gltf.buffers[bufferView.buffer]; 123 | const source = buffer.extras._pipeline.source; 124 | const byteOffset = defaultValue(bufferView.byteOffset, 0); 125 | return source.slice(byteOffset, byteOffset + bufferView.byteLength); 126 | } 127 | 128 | function readFile(object, uri, saveResourceId, options) { 129 | const resourceDirectory = options.resourceDirectory; 130 | const hasResourceDirectory = defined(resourceDirectory); 131 | 132 | // Resolve the URL 133 | let absoluteUrl; 134 | try { 135 | absoluteUrl = new URL( 136 | uri, 137 | hasResourceDirectory ? pathToFileURL(resourceDirectory) : undefined, 138 | ); 139 | } catch (error) { 140 | return Promise.reject( 141 | new RuntimeError( 142 | "glTF model references separate files but no resourceDirectory is supplied", 143 | ), 144 | ); 145 | } 146 | 147 | // Generate file paths for the resource 148 | const absolutePath = fileURLToPath(absoluteUrl); 149 | const relativePath = hasResourceDirectory 150 | ? path.relative(resourceDirectory, absolutePath) 151 | : path.basename(absolutePath); 152 | 153 | if (!defined(object.name)) { 154 | const extension = path.extname(relativePath); 155 | object.name = path.basename(relativePath, extension); 156 | } 157 | 158 | if (saveResourceId) { 159 | object.extras._pipeline.resourceId = absolutePath; 160 | } 161 | 162 | object.extras._pipeline.absolutePath = absolutePath; 163 | object.extras._pipeline.relativePath = relativePath; 164 | return fsExtra.readFile(absolutePath); 165 | } 166 | -------------------------------------------------------------------------------- /lib/removeDefaults.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const ForEach = require("./ForEach"); 4 | 5 | const defined = Cesium.defined; 6 | const Matrix4 = Cesium.Matrix4; 7 | 8 | module.exports = removeDefaults; 9 | 10 | /** 11 | * Remove default values from the glTF. Not exhaustive. 12 | * 13 | * @param {object} gltf A javascript object containing a glTF asset. 14 | * @returns {object} glTF with default values removed. 15 | * 16 | * @private 17 | */ 18 | function removeDefaults(gltf) { 19 | ForEach.node(gltf, function (node) { 20 | if ( 21 | defined(node.matrix) && 22 | Matrix4.equals(Matrix4.fromArray(node.matrix), Matrix4.IDENTITY) 23 | ) { 24 | delete node.matrix; 25 | } 26 | }); 27 | ForEach.accessor(gltf, function (accessor) { 28 | if (accessor.normalized === false) { 29 | delete accessor.normalized; 30 | } 31 | }); 32 | return gltf; 33 | } 34 | -------------------------------------------------------------------------------- /lib/removeExtension.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const ForEach = require("./ForEach"); 4 | const removeExtensionsUsed = require("./removeExtensionsUsed"); 5 | 6 | const defined = Cesium.defined; 7 | 8 | module.exports = removeExtension; 9 | 10 | /** 11 | * Removes an extension from gltf.extensions, gltf.extensionsUsed, gltf.extensionsRequired, and any other objects in the glTF if it is present. 12 | * 13 | * @param {object} gltf A javascript object containing a glTF asset. 14 | * @param {string} extension The extension to remove. 15 | * 16 | * @returns {*} The extension data removed from gltf.extensions. 17 | */ 18 | function removeExtension(gltf, extension) { 19 | removeExtensionsUsed(gltf, extension); // Also removes from extensionsRequired 20 | 21 | if (extension === "CESIUM_RTC") { 22 | removeCesiumRTC(gltf); 23 | } 24 | 25 | return removeExtensionAndTraverse(gltf, extension); 26 | } 27 | 28 | function removeCesiumRTC(gltf) { 29 | ForEach.technique(gltf, function (technique) { 30 | ForEach.techniqueUniform(technique, function (uniform) { 31 | if (uniform.semantic === "CESIUM_RTC_MODELVIEW") { 32 | uniform.semantic = "MODELVIEW"; 33 | } 34 | }); 35 | }); 36 | } 37 | 38 | function removeExtensionAndTraverse(object, extension) { 39 | if (Array.isArray(object)) { 40 | const length = object.length; 41 | for (let i = 0; i < length; ++i) { 42 | removeExtensionAndTraverse(object[i], extension); 43 | } 44 | } else if ( 45 | object !== null && 46 | typeof object === "object" && 47 | object.constructor === Object 48 | ) { 49 | const extensions = object.extensions; 50 | let extensionData; 51 | if (defined(extensions)) { 52 | extensionData = extensions[extension]; 53 | if (defined(extensionData)) { 54 | delete extensions[extension]; 55 | if (Object.keys(extensions).length === 0) { 56 | delete object.extensions; 57 | } 58 | } 59 | } 60 | for (const key in object) { 61 | if (Object.prototype.hasOwnProperty.call(object, key)) { 62 | removeExtensionAndTraverse(object[key], extension); 63 | } 64 | } 65 | return extensionData; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/removeExtensionsRequired.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | 4 | const defined = Cesium.defined; 5 | 6 | module.exports = removeExtensionsRequired; 7 | 8 | /** 9 | * Removes an extension from gltf.extensionsRequired if it is present. 10 | * 11 | * @param {object} gltf A javascript object containing a glTF asset. 12 | * @param {string} extension The extension to remove. 13 | * 14 | * @private 15 | */ 16 | function removeExtensionsRequired(gltf, extension) { 17 | const extensionsRequired = gltf.extensionsRequired; 18 | if (defined(extensionsRequired)) { 19 | const index = extensionsRequired.indexOf(extension); 20 | if (index >= 0) { 21 | extensionsRequired.splice(index, 1); 22 | } 23 | if (extensionsRequired.length === 0) { 24 | delete gltf.extensionsRequired; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/removeExtensionsUsed.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const removeExtensionsRequired = require("./removeExtensionsRequired"); 4 | 5 | const defined = Cesium.defined; 6 | 7 | module.exports = removeExtensionsUsed; 8 | 9 | /** 10 | * Removes an extension from gltf.extensionsUsed and gltf.extensionsRequired if it is present. 11 | * 12 | * @param {object} gltf A javascript object containing a glTF asset. 13 | * @param {string} extension The extension to remove. 14 | * 15 | * @private 16 | */ 17 | function removeExtensionsUsed(gltf, extension) { 18 | const extensionsUsed = gltf.extensionsUsed; 19 | if (defined(extensionsUsed)) { 20 | const index = extensionsUsed.indexOf(extension); 21 | if (index >= 0) { 22 | extensionsUsed.splice(index, 1); 23 | } 24 | removeExtensionsRequired(gltf, extension); 25 | if (extensionsUsed.length === 0) { 26 | delete gltf.extensionsUsed; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/removePipelineExtras.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const ForEach = require("./ForEach"); 4 | 5 | const defined = Cesium.defined; 6 | 7 | module.exports = removePipelineExtras; 8 | 9 | /** 10 | * Iterate through the objects within the glTF and delete their pipeline extras object. 11 | * 12 | * @param {object} gltf A javascript object containing a glTF asset. 13 | * @returns {object} glTF with no pipeline extras. 14 | * 15 | * @private 16 | */ 17 | function removePipelineExtras(gltf) { 18 | ForEach.shader(gltf, function (shader) { 19 | removeExtras(shader); 20 | }); 21 | ForEach.buffer(gltf, function (buffer) { 22 | removeExtras(buffer); 23 | }); 24 | ForEach.image(gltf, function (image) { 25 | removeExtras(image); 26 | }); 27 | 28 | removeExtras(gltf); 29 | 30 | return gltf; 31 | } 32 | 33 | function removeExtras(object) { 34 | if (!defined(object.extras)) { 35 | return; 36 | } 37 | 38 | if (defined(object.extras._pipeline)) { 39 | delete object.extras._pipeline; 40 | } 41 | 42 | if (Object.keys(object.extras).length === 0) { 43 | delete object.extras; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/updateAccessorComponentTypes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const addBuffer = require("./addBuffer"); 4 | const ForEach = require("./ForEach"); 5 | const readAccessorPacked = require("./readAccessorPacked"); 6 | 7 | const ComponentDatatype = Cesium.ComponentDatatype; 8 | const WebGLConstants = Cesium.WebGLConstants; 9 | 10 | module.exports = updateAccessorComponentTypes; 11 | 12 | /** 13 | * Update accessors referenced by JOINTS_0 and WEIGHTS_0 attributes to use correct component types. 14 | * 15 | * @param {object} gltf A javascript object containing a glTF asset. 16 | * @returns {object} The glTF asset with compressed meshes. 17 | * 18 | * @private 19 | */ 20 | function updateAccessorComponentTypes(gltf) { 21 | let componentType; 22 | ForEach.accessorWithSemantic(gltf, "JOINTS_0", function (accessorId) { 23 | const accessor = gltf.accessors[accessorId]; 24 | componentType = accessor.componentType; 25 | if (componentType === WebGLConstants.BYTE) { 26 | convertType(gltf, accessor, ComponentDatatype.UNSIGNED_BYTE); 27 | } else if ( 28 | componentType !== WebGLConstants.UNSIGNED_BYTE && 29 | componentType !== WebGLConstants.UNSIGNED_SHORT 30 | ) { 31 | convertType(gltf, accessor, ComponentDatatype.UNSIGNED_SHORT); 32 | } 33 | }); 34 | ForEach.accessorWithSemantic(gltf, "WEIGHTS_0", function (accessorId) { 35 | const accessor = gltf.accessors[accessorId]; 36 | componentType = accessor.componentType; 37 | if (componentType === WebGLConstants.BYTE) { 38 | convertType(gltf, accessor, ComponentDatatype.UNSIGNED_BYTE); 39 | } else if (componentType === WebGLConstants.SHORT) { 40 | convertType(gltf, accessor, ComponentDatatype.UNSIGNED_SHORT); 41 | } 42 | }); 43 | 44 | return gltf; 45 | } 46 | 47 | function convertType(gltf, accessor, updatedComponentType) { 48 | const typedArray = ComponentDatatype.createTypedArray( 49 | updatedComponentType, 50 | readAccessorPacked(gltf, accessor), 51 | ); 52 | const newBuffer = new Uint8Array(typedArray.buffer); 53 | accessor.bufferView = addBuffer(gltf, newBuffer); 54 | accessor.componentType = updatedComponentType; 55 | accessor.byteOffset = 0; 56 | } 57 | -------------------------------------------------------------------------------- /lib/usesExtension.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | 4 | const defined = Cesium.defined; 5 | 6 | module.exports = usesExtension; 7 | 8 | /** 9 | * Checks whether the glTF uses the given extension. 10 | * 11 | * @param {object} gltf A javascript object containing a glTF asset. 12 | * @param {string} extension The name of the extension. 13 | * @returns {boolean} Whether the glTF uses the given extension. 14 | * 15 | * @private 16 | */ 17 | function usesExtension(gltf, extension) { 18 | return ( 19 | defined(gltf.extensionsUsed) && gltf.extensionsUsed.indexOf(extension) >= 0 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gltf-pipeline", 3 | "version": "4.1.0", 4 | "description": "Content pipeline tools for optimizing glTF assets.", 5 | "license": "Apache-2.0", 6 | "contributors": [ 7 | { 8 | "name": "Richard Lee, Cesium GS, Inc., and Contributors", 9 | "url": "https://github.com/CesiumGS/gltf-pipeline/graphs/contributors" 10 | } 11 | ], 12 | "keywords": [ 13 | "glTF", 14 | "WebGL" 15 | ], 16 | "homepage": "https://github.com/CesiumGS/gltf-pipeline", 17 | "repository": { 18 | "type": "git", 19 | "url": "git@github.com:CesiumGS/gltf-pipeline.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/CesiumGS/gltf-pipeline/issues" 23 | }, 24 | "main": "index.js", 25 | "engines": { 26 | "node": ">=16.0.0" 27 | }, 28 | "dependencies": { 29 | "bluebird": "^3.7.2", 30 | "cesium": "^1.86.1", 31 | "draco3d": "^1.4.3", 32 | "fs-extra": "^11.0.0", 33 | "mime": "^3.0.0", 34 | "object-hash": "^3.0.0", 35 | "yargs": "^17.2.1" 36 | }, 37 | "devDependencies": { 38 | "cloc": "^2.8.0", 39 | "dependency-tree": "^10.0.9", 40 | "eslint": "^8.0.1", 41 | "eslint-config-cesium": "^10.0.1", 42 | "eslint-config-prettier": "^9.0.0", 43 | "eslint-plugin-n": "^16.1.0", 44 | "gulp": "^4.0.2", 45 | "husky": "^8.0.3", 46 | "jasmine": "^5.0.0", 47 | "jasmine-spec-reporter": "^7.0.0", 48 | "jsdoc": "^4.0.0", 49 | "nyc": "^15.1.0", 50 | "prettier": "3.1.1", 51 | "lint-staged": "^15.0.2" 52 | }, 53 | "lint-staged": { 54 | "*.(js|ts)": [ 55 | "eslint --cache --quiet --fix", 56 | "prettier --write" 57 | ], 58 | "*.!(js|ts)": "prettier --write" 59 | }, 60 | "scripts": { 61 | "prepare": "husky install", 62 | "pre-commit": "lint-staged", 63 | "jsdoc": "jsdoc ./lib -R ./README.md -d doc", 64 | "eslint-fix": "eslint \"./**/*.js\" --fix", 65 | "eslint": "eslint \"./**/*.js\" --cache --quiet", 66 | "eslint-watch": "gulp eslint-watch", 67 | "test": "gulp test", 68 | "test-watch": "gulp test-watch", 69 | "coverage": "gulp coverage", 70 | "cloc": "gulp cloc", 71 | "prettier": "prettier --write \"**/*\"", 72 | "prettier-check": "prettier --check \"**/*\"", 73 | "build-cesium": "gulp build-cesium", 74 | "generate-third-party": "gulp generate-third-party" 75 | }, 76 | "bin": { 77 | "gltf-pipeline": "./bin/gltf-pipeline.js" 78 | } 79 | } -------------------------------------------------------------------------------- /specs/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.eslintrc.json", 3 | "env": { 4 | "jasmine": true 5 | }, 6 | "rules": { 7 | "no-restricted-globals": ["error", "fdescribe", "fit"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /specs/data/1.0/box-materials-common/box-materials-common.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/1.0/box-materials-common/box-materials-common.bin -------------------------------------------------------------------------------- /specs/data/1.0/box-materials-common/box-materials-common.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "accessors": { 3 | "accessor_21": { 4 | "bufferView": "bufferView_29", 5 | "byteOffset": 0, 6 | "byteStride": 0, 7 | "componentType": 5123, 8 | "count": 36, 9 | "type": "SCALAR" 10 | }, 11 | "accessor_23": { 12 | "bufferView": "bufferView_30", 13 | "byteOffset": 0, 14 | "byteStride": 12, 15 | "componentType": 5126, 16 | "count": 24, 17 | "max": [ 18 | 0.5, 19 | 0.5, 20 | 0.5 21 | ], 22 | "min": [ 23 | -0.5, 24 | -0.5, 25 | -0.5 26 | ], 27 | "type": "VEC3" 28 | }, 29 | "accessor_25": { 30 | "bufferView": "bufferView_30", 31 | "byteOffset": 288, 32 | "byteStride": 12, 33 | "componentType": 5126, 34 | "count": 24, 35 | "max": [ 36 | 1, 37 | 1, 38 | 1 39 | ], 40 | "min": [ 41 | -1, 42 | -1, 43 | -1 44 | ], 45 | "type": "VEC3" 46 | } 47 | }, 48 | "animations": {}, 49 | "asset": { 50 | "generator": "collada2gltf@027f74366341d569dea42e9a68b7104cc3892054", 51 | "premultipliedAlpha": true, 52 | "profile": { 53 | "api": "WebGL", 54 | "version": "1.0.2" 55 | }, 56 | "version": "1.0" 57 | }, 58 | "bufferViews": { 59 | "bufferView_29": { 60 | "buffer": "Box", 61 | "byteLength": 72, 62 | "byteOffset": 0, 63 | "target": 34963 64 | }, 65 | "bufferView_30": { 66 | "buffer": "Box", 67 | "byteLength": 576, 68 | "byteOffset": 72, 69 | "target": 34962 70 | } 71 | }, 72 | "buffers": { 73 | "Box": { 74 | "byteLength": 648, 75 | "type": "arraybuffer", 76 | "uri": "box-materials-common.bin" 77 | } 78 | }, 79 | "extensionsUsed": [ 80 | "KHR_materials_common" 81 | ], 82 | "materials": { 83 | "Effect-Red": { 84 | "extensions": { 85 | "KHR_materials_common": { 86 | "doubleSided": false, 87 | "jointCount": 0, 88 | "technique": "PHONG", 89 | "transparent": false, 90 | "values": { 91 | "diffuse": [ 92 | 0.8, 93 | 0, 94 | 0, 95 | 1 96 | ], 97 | "shininess": 256, 98 | "specular": [ 99 | 0.2, 100 | 0.2, 101 | 0.2, 102 | 1 103 | ] 104 | } 105 | } 106 | }, 107 | "name": "Red" 108 | } 109 | }, 110 | "meshes": { 111 | "Geometry-mesh002": { 112 | "name": "Mesh", 113 | "primitives": [ 114 | { 115 | "attributes": { 116 | "NORMAL": "accessor_25", 117 | "POSITION": "accessor_23" 118 | }, 119 | "indices": "accessor_21", 120 | "material": "Effect-Red", 121 | "mode": 4 122 | } 123 | ] 124 | } 125 | }, 126 | "nodes": { 127 | "Geometry-mesh002Node": { 128 | "children": [], 129 | "matrix": [ 130 | 1, 131 | 0, 132 | 0, 133 | 0, 134 | 0, 135 | 1, 136 | 0, 137 | 0, 138 | 0, 139 | 0, 140 | 1, 141 | 0, 142 | 0, 143 | 0, 144 | 0, 145 | 1 146 | ], 147 | "meshes": [ 148 | "Geometry-mesh002" 149 | ], 150 | "name": "Mesh" 151 | }, 152 | "node_1": { 153 | "children": [ 154 | "Geometry-mesh002Node" 155 | ], 156 | "matrix": [ 157 | 1, 158 | 0, 159 | 0, 160 | 0, 161 | 0, 162 | 0, 163 | -1, 164 | 0, 165 | 0, 166 | 1, 167 | 0, 168 | 0, 169 | 0, 170 | 0, 171 | 0, 172 | 1 173 | ], 174 | "name": "Y_UP_Transform" 175 | } 176 | }, 177 | "scene": "defaultScene", 178 | "scenes": { 179 | "defaultScene": { 180 | "nodes": [ 181 | "node_1" 182 | ] 183 | } 184 | }, 185 | "skins": {} 186 | } -------------------------------------------------------------------------------- /specs/data/1.0/box-textured-binary-separate/box-textured-binary-separate-fs.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | varying vec3 v_normal; 3 | varying vec2 v_texcoord0; 4 | uniform sampler2D u_diffuse; 5 | uniform vec4 u_specular; 6 | uniform float u_shininess; 7 | void main(void) { 8 | vec3 normal = normalize(v_normal); 9 | vec4 color = vec4(0., 0., 0., 0.); 10 | vec4 diffuse = vec4(0., 0., 0., 1.); 11 | vec4 specular; 12 | diffuse = texture2D(u_diffuse, v_texcoord0); 13 | specular = u_specular; 14 | diffuse.xyz *= max(dot(normal,vec3(0.,0.,1.)), 0.); 15 | color.xyz += diffuse.xyz; 16 | color = vec4(color.rgb * diffuse.a, diffuse.a); 17 | gl_FragColor = color; 18 | } 19 | -------------------------------------------------------------------------------- /specs/data/1.0/box-textured-binary-separate/box-textured-binary-separate-vs.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | attribute vec3 a_position; 3 | attribute vec3 a_normal; 4 | varying vec3 v_normal; 5 | uniform mat3 u_normalMatrix; 6 | uniform mat4 u_modelViewMatrix; 7 | uniform mat4 u_projectionMatrix; 8 | attribute vec2 a_texcoord0; 9 | varying vec2 v_texcoord0; 10 | void main(void) { 11 | vec4 pos = u_modelViewMatrix * vec4(a_position,1.0); 12 | v_normal = u_normalMatrix * a_normal; 13 | v_texcoord0 = a_texcoord0; 14 | gl_Position = u_projectionMatrix * pos; 15 | } 16 | -------------------------------------------------------------------------------- /specs/data/1.0/box-textured-binary-separate/box-textured-binary-separate.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/1.0/box-textured-binary-separate/box-textured-binary-separate.glb -------------------------------------------------------------------------------- /specs/data/1.0/box-textured-binary-separate/cesium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/1.0/box-textured-binary-separate/cesium.png -------------------------------------------------------------------------------- /specs/data/1.0/box-textured-binary/box-textured-binary.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/1.0/box-textured-binary/box-textured-binary.glb -------------------------------------------------------------------------------- /specs/data/1.0/box-textured-materials-common/CesiumLogoFlat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/1.0/box-textured-materials-common/CesiumLogoFlat.png -------------------------------------------------------------------------------- /specs/data/1.0/box-textured-materials-common/box-textured-materials-common.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/1.0/box-textured-materials-common/box-textured-materials-common.bin -------------------------------------------------------------------------------- /specs/data/1.0/box-textured-materials-common/box-textured-materials-common.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "accessors": { 3 | "accessor_21": { 4 | "bufferView": "bufferView_29", 5 | "byteOffset": 0, 6 | "byteStride": 0, 7 | "componentType": 5123, 8 | "count": 36, 9 | "type": "SCALAR" 10 | }, 11 | "accessor_23": { 12 | "bufferView": "bufferView_30", 13 | "byteOffset": 0, 14 | "byteStride": 12, 15 | "componentType": 5126, 16 | "count": 24, 17 | "max": [ 18 | 0.5, 19 | 0.5, 20 | 0.5 21 | ], 22 | "min": [ 23 | -0.5, 24 | -0.5, 25 | -0.5 26 | ], 27 | "type": "VEC3" 28 | }, 29 | "accessor_25": { 30 | "bufferView": "bufferView_30", 31 | "byteOffset": 288, 32 | "byteStride": 12, 33 | "componentType": 5126, 34 | "count": 24, 35 | "max": [ 36 | 1, 37 | 1, 38 | 1 39 | ], 40 | "min": [ 41 | -1, 42 | -1, 43 | -1 44 | ], 45 | "type": "VEC3" 46 | }, 47 | "accessor_27": { 48 | "bufferView": "bufferView_30", 49 | "byteOffset": 576, 50 | "byteStride": 8, 51 | "componentType": 5126, 52 | "count": 24, 53 | "max": [ 54 | 6, 55 | 1 56 | ], 57 | "min": [ 58 | 0, 59 | 0 60 | ], 61 | "type": "VEC2" 62 | } 63 | }, 64 | "animations": {}, 65 | "asset": { 66 | "generator": "collada2gltf@027f74366341d569dea42e9a68b7104cc3892054", 67 | "premultipliedAlpha": true, 68 | "profile": { 69 | "api": "WebGL", 70 | "version": "1.0.2" 71 | }, 72 | "version": "1.0" 73 | }, 74 | "bufferViews": { 75 | "bufferView_29": { 76 | "buffer": "BoxTextured", 77 | "byteLength": 72, 78 | "byteOffset": 0, 79 | "target": 34963 80 | }, 81 | "bufferView_30": { 82 | "buffer": "BoxTextured", 83 | "byteLength": 768, 84 | "byteOffset": 72, 85 | "target": 34962 86 | } 87 | }, 88 | "buffers": { 89 | "BoxTextured": { 90 | "byteLength": 840, 91 | "type": "arraybuffer", 92 | "uri": "box-textured-materials-common.bin" 93 | } 94 | }, 95 | "extensionsUsed": [ 96 | "KHR_materials_common" 97 | ], 98 | "images": { 99 | "Image0001": { 100 | "name": "Image0001", 101 | "uri": "CesiumLogoFlat.png" 102 | } 103 | }, 104 | "materials": { 105 | "Effect-Texture": { 106 | "extensions": { 107 | "KHR_materials_common": { 108 | "doubleSided": false, 109 | "jointCount": 0, 110 | "technique": "PHONG", 111 | "transparent": false, 112 | "values": { 113 | "diffuse": "texture_Image0001", 114 | "shininess": 256, 115 | "specular": [ 116 | 0.2, 117 | 0.2, 118 | 0.2, 119 | 1 120 | ] 121 | } 122 | } 123 | }, 124 | "name": "Texture" 125 | } 126 | }, 127 | "meshes": { 128 | "Geometry-mesh002": { 129 | "name": "Mesh", 130 | "primitives": [ 131 | { 132 | "attributes": { 133 | "NORMAL": "accessor_25", 134 | "POSITION": "accessor_23", 135 | "TEXCOORD_0": "accessor_27" 136 | }, 137 | "indices": "accessor_21", 138 | "material": "Effect-Texture", 139 | "mode": 4 140 | } 141 | ] 142 | } 143 | }, 144 | "nodes": { 145 | "Geometry-mesh002Node": { 146 | "children": [], 147 | "matrix": [ 148 | 1, 149 | 0, 150 | 0, 151 | 0, 152 | 0, 153 | 1, 154 | 0, 155 | 0, 156 | 0, 157 | 0, 158 | 1, 159 | 0, 160 | 0, 161 | 0, 162 | 0, 163 | 1 164 | ], 165 | "meshes": [ 166 | "Geometry-mesh002" 167 | ], 168 | "name": "Mesh" 169 | }, 170 | "groupLocator030Node": { 171 | "children": [ 172 | "txtrLocator026Node" 173 | ], 174 | "matrix": [ 175 | 1, 176 | 0, 177 | 0, 178 | 0, 179 | 0, 180 | 1, 181 | 0, 182 | 0, 183 | 0, 184 | 0, 185 | 1, 186 | 0, 187 | 0, 188 | 0, 189 | 0, 190 | 1 191 | ], 192 | "name": "Texture_Group" 193 | }, 194 | "node_3": { 195 | "children": [ 196 | "Geometry-mesh002Node", 197 | "groupLocator030Node" 198 | ], 199 | "matrix": [ 200 | 1, 201 | 0, 202 | 0, 203 | 0, 204 | 0, 205 | 0, 206 | -1, 207 | 0, 208 | 0, 209 | 1, 210 | 0, 211 | 0, 212 | 0, 213 | 0, 214 | 0, 215 | 1 216 | ], 217 | "name": "Y_UP_Transform" 218 | }, 219 | "txtrLocator026Node": { 220 | "children": [], 221 | "matrix": [ 222 | 1, 223 | 0, 224 | 0, 225 | 0, 226 | 0, 227 | 1, 228 | 0, 229 | 0, 230 | 0, 231 | 0, 232 | 1, 233 | 0, 234 | 0, 235 | 0, 236 | 0, 237 | 1 238 | ], 239 | "name": "Cesium_Logo_Flat__Image___Texture_" 240 | } 241 | }, 242 | "samplers": { 243 | "sampler_0": { 244 | "magFilter": 9729, 245 | "minFilter": 9987, 246 | "wrapS": 10497, 247 | "wrapT": 10497 248 | } 249 | }, 250 | "scene": "defaultScene", 251 | "scenes": { 252 | "defaultScene": { 253 | "nodes": [ 254 | "node_3" 255 | ] 256 | } 257 | }, 258 | "skins": {}, 259 | "textures": { 260 | "texture_Image0001": { 261 | "format": 6408, 262 | "internalFormat": 6408, 263 | "sampler": "sampler_0", 264 | "source": "Image0001", 265 | "target": 3553, 266 | "type": 5121 267 | } 268 | } 269 | } -------------------------------------------------------------------------------- /specs/data/1.0/box-textured-separate/box-textured-separate-fs.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | varying vec3 v_normal; 3 | varying vec2 v_texcoord0; 4 | uniform sampler2D u_diffuse; 5 | uniform vec4 u_specular; 6 | uniform float u_shininess; 7 | void main(void) { 8 | vec3 normal = normalize(v_normal); 9 | vec4 color = vec4(0., 0., 0., 0.); 10 | vec4 diffuse = vec4(0., 0., 0., 1.); 11 | vec4 specular; 12 | diffuse = texture2D(u_diffuse, v_texcoord0); 13 | specular = u_specular; 14 | diffuse.xyz *= max(dot(normal,vec3(0.,0.,1.)), 0.); 15 | color.xyz += diffuse.xyz; 16 | color = vec4(color.rgb * diffuse.a, diffuse.a); 17 | gl_FragColor = color; 18 | } 19 | -------------------------------------------------------------------------------- /specs/data/1.0/box-textured-separate/box-textured-separate-vs.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | attribute vec3 a_position; 3 | attribute vec3 a_normal; 4 | varying vec3 v_normal; 5 | uniform mat3 u_normalMatrix; 6 | uniform mat4 u_modelViewMatrix; 7 | uniform mat4 u_projectionMatrix; 8 | attribute vec2 a_texcoord0; 9 | varying vec2 v_texcoord0; 10 | void main(void) { 11 | vec4 pos = u_modelViewMatrix * vec4(a_position,1.0); 12 | v_normal = u_normalMatrix * a_normal; 13 | v_texcoord0 = a_texcoord0; 14 | gl_Position = u_projectionMatrix * pos; 15 | } 16 | -------------------------------------------------------------------------------- /specs/data/1.0/box-textured-separate/box-textured-separate.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/1.0/box-textured-separate/box-textured-separate.bin -------------------------------------------------------------------------------- /specs/data/1.0/box-textured-separate/cesium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/1.0/box-textured-separate/cesium.png -------------------------------------------------------------------------------- /specs/data/2.0/box-shared-image-references-separate/CesiumTexturedBoxTest0FS.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | varying vec3 v_normal; 3 | varying vec2 v_texcoord0; 4 | uniform sampler2D u_diffuse; 5 | uniform vec4 u_specular; 6 | uniform float u_shininess; 7 | void main(void) { 8 | vec3 normal = normalize(v_normal); 9 | vec4 color = vec4(0., 0., 0., 0.); 10 | vec4 diffuse = vec4(0., 0., 0., 1.); 11 | vec4 specular; 12 | diffuse = texture2D(u_diffuse, v_texcoord0); 13 | specular = u_specular; 14 | diffuse.xyz *= max(dot(normal,vec3(0.,0.,1.)), 0.); 15 | color.xyz += diffuse.xyz; 16 | color = vec4(color.rgb * diffuse.a, diffuse.a); 17 | gl_FragColor = color; 18 | } 19 | -------------------------------------------------------------------------------- /specs/data/2.0/box-shared-image-references-separate/CesiumTexturedBoxTest0VS.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | attribute vec3 a_position; 3 | attribute vec3 a_normal; 4 | varying vec3 v_normal; 5 | uniform mat3 u_normalMatrix; 6 | uniform mat4 u_modelViewMatrix; 7 | uniform mat4 u_projectionMatrix; 8 | attribute vec2 a_texcoord0; 9 | varying vec2 v_texcoord0; 10 | void main(void) { 11 | vec4 pos = u_modelViewMatrix * vec4(a_position,1.0); 12 | v_normal = u_normalMatrix * a_normal; 13 | v_texcoord0 = a_texcoord0; 14 | gl_Position = u_projectionMatrix * pos; 15 | } 16 | -------------------------------------------------------------------------------- /specs/data/2.0/box-shared-image-references-separate/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/2.0/box-shared-image-references-separate/Image.png -------------------------------------------------------------------------------- /specs/data/2.0/box-shared-image-references-separate/box-shared-image-references-separate.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/2.0/box-shared-image-references-separate/box-shared-image-references-separate.bin -------------------------------------------------------------------------------- /specs/data/2.0/box-shared-image-references-separate/box-shared-image-references-separate.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "accessors": [ 3 | { 4 | "bufferView": 2, 5 | "byteOffset": 0, 6 | "componentType": 5123, 7 | "count": 36, 8 | "type": "SCALAR", 9 | "name": "accessor_indices" 10 | }, 11 | { 12 | "bufferView": 0, 13 | "byteOffset": 0, 14 | "componentType": 5126, 15 | "count": 24, 16 | "max": [ 17 | 0.5, 18 | 0.5, 19 | 0.5 20 | ], 21 | "min": [ 22 | -0.5, 23 | -0.5, 24 | -0.5 25 | ], 26 | "type": "VEC3", 27 | "name": "accessor_positions" 28 | }, 29 | { 30 | "bufferView": 0, 31 | "byteOffset": 288, 32 | "componentType": 5126, 33 | "count": 24, 34 | "max": [ 35 | 1, 36 | 1, 37 | 1 38 | ], 39 | "min": [ 40 | -1, 41 | -1, 42 | -1 43 | ], 44 | "type": "VEC3", 45 | "name": "accessor_normals" 46 | }, 47 | { 48 | "bufferView": 1, 49 | "byteOffset": 0, 50 | "componentType": 5126, 51 | "count": 24, 52 | "max": [ 53 | 6, 54 | 1 55 | ], 56 | "min": [ 57 | 0, 58 | 0 59 | ], 60 | "type": "VEC2", 61 | "name": "accessor_texcoord" 62 | } 63 | ], 64 | "asset": { 65 | "generator": "collada2gltf@ceec062e3d5793f2f249f53cbd843aee382ad40b", 66 | "version": "2.0" 67 | }, 68 | "bufferViews": [ 69 | { 70 | "buffer": 0, 71 | "byteLength": 576, 72 | "byteOffset": 0, 73 | "target": 34962, 74 | "name": "bufferView_0", 75 | "byteStride": 12 76 | }, 77 | { 78 | "buffer": 0, 79 | "byteLength": 192, 80 | "byteOffset": 576, 81 | "target": 34962, 82 | "name": "bufferView_0", 83 | "byteStride": 8 84 | }, 85 | { 86 | "buffer": 0, 87 | "byteLength": 72, 88 | "byteOffset": 768, 89 | "target": 34963, 90 | "name": "bufferView_1" 91 | } 92 | ], 93 | "buffers": [ 94 | { 95 | "name": "binary_glTF", 96 | "byteLength": 840, 97 | "uri": "box-shared-image-references-separate.bin" 98 | } 99 | ], 100 | "images": [ 101 | { 102 | "mimeType": "image/png", 103 | "uri": "Image.png" 104 | }, 105 | { 106 | "mimeType": "image/png", 107 | "uri": "Image.png" 108 | } 109 | ], 110 | "materials": [ 111 | { 112 | "name": "Texture", 113 | "extensions": { 114 | "KHR_techniques_webgl": { 115 | "technique": 0, 116 | "values": { 117 | "u_diffuse": { 118 | "index": 0, 119 | "texCoord": 0 120 | }, 121 | "u_shininess": 256, 122 | "u_specular": [ 123 | 0.2, 124 | 0.2, 125 | 0.2, 126 | 1 127 | ] 128 | } 129 | } 130 | }, 131 | "emissiveFactor": [ 132 | 0, 133 | 0, 134 | 0 135 | ], 136 | "alphaMode": "OPAQUE", 137 | "doubleSided": false 138 | } 139 | ], 140 | "meshes": [ 141 | { 142 | "name": "Mesh", 143 | "primitives": [ 144 | { 145 | "attributes": { 146 | "NORMAL": 2, 147 | "POSITION": 1, 148 | "TEXCOORD_0": 3 149 | }, 150 | "indices": 0, 151 | "material": 0, 152 | "mode": 4 153 | } 154 | ] 155 | } 156 | ], 157 | "nodes": [ 158 | { 159 | "name": "rootNode", 160 | "mesh": 0 161 | } 162 | ], 163 | "samplers": [ 164 | { 165 | "magFilter": 9729, 166 | "minFilter": 9987, 167 | "wrapS": 10497, 168 | "wrapT": 10497, 169 | "name": "sampler_0" 170 | } 171 | ], 172 | "scene": 0, 173 | "scenes": [ 174 | { 175 | "nodes": [ 176 | 0 177 | ], 178 | "name": "defaultScene" 179 | } 180 | ], 181 | "textures": [ 182 | { 183 | "sampler": 0, 184 | "source": 0, 185 | "name": "texture_Image0001" 186 | }, 187 | { 188 | "sampler": 0, 189 | "source": 1, 190 | "name": "texture_Image0002" 191 | } 192 | ], 193 | "extensionsRequired": [ 194 | "KHR_techniques_webgl" 195 | ], 196 | "extensions": { 197 | "KHR_techniques_webgl": { 198 | "programs": [ 199 | { 200 | "name": "program_0", 201 | "fragmentShader": 0, 202 | "vertexShader": 1 203 | } 204 | ], 205 | "shaders": [ 206 | { 207 | "type": 35632, 208 | "name": "CesiumTexturedBoxTest0FS", 209 | "uri": "CesiumTexturedBoxTest0FS.glsl" 210 | }, 211 | { 212 | "type": 35633, 213 | "name": "CesiumTexturedBoxTest0VS", 214 | "uri": "CesiumTexturedBoxTest0VS.glsl" 215 | } 216 | ], 217 | "techniques": [ 218 | { 219 | "name": "technique0", 220 | "program": 0, 221 | "attributes": { 222 | "a_normal": { 223 | "semantic": "NORMAL" 224 | }, 225 | "a_position": { 226 | "semantic": "POSITION" 227 | }, 228 | "a_texcoord0": { 229 | "semantic": "TEXCOORD_0" 230 | } 231 | }, 232 | "uniforms": { 233 | "u_diffuse": { 234 | "type": 35678 235 | }, 236 | "u_modelViewMatrix": { 237 | "type": 35676, 238 | "semantic": "MODELVIEW" 239 | }, 240 | "u_normalMatrix": { 241 | "type": 35675, 242 | "semantic": "MODELVIEWINVERSETRANSPOSE" 243 | }, 244 | "u_projectionMatrix": { 245 | "type": 35676, 246 | "semantic": "PROJECTION" 247 | }, 248 | "u_shininess": { 249 | "type": 5126 250 | }, 251 | "u_specular": { 252 | "type": 35666 253 | } 254 | } 255 | } 256 | ] 257 | } 258 | }, 259 | "extensionsUsed": [ 260 | "KHR_techniques_webgl" 261 | ] 262 | } 263 | -------------------------------------------------------------------------------- /specs/data/2.0/box-techniques-separate/CesiumTexturedBoxTest.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/2.0/box-techniques-separate/CesiumTexturedBoxTest.bin -------------------------------------------------------------------------------- /specs/data/2.0/box-techniques-separate/CesiumTexturedBoxTest0FS.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | varying vec3 v_normal; 3 | varying vec2 v_texcoord0; 4 | uniform sampler2D u_diffuse; 5 | uniform vec4 u_specular; 6 | uniform float u_shininess; 7 | void main(void) { 8 | vec3 normal = normalize(v_normal); 9 | vec4 color = vec4(0., 0., 0., 0.); 10 | vec4 diffuse = vec4(0., 0., 0., 1.); 11 | vec4 specular; 12 | diffuse = texture2D(u_diffuse, v_texcoord0); 13 | specular = u_specular; 14 | diffuse.xyz *= max(dot(normal,vec3(0.,0.,1.)), 0.); 15 | color.xyz += diffuse.xyz; 16 | color = vec4(color.rgb * diffuse.a, diffuse.a); 17 | gl_FragColor = color; 18 | } 19 | -------------------------------------------------------------------------------- /specs/data/2.0/box-techniques-separate/CesiumTexturedBoxTest0VS.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | attribute vec3 a_position; 3 | attribute vec3 a_normal; 4 | varying vec3 v_normal; 5 | uniform mat3 u_normalMatrix; 6 | uniform mat4 u_modelViewMatrix; 7 | uniform mat4 u_projectionMatrix; 8 | attribute vec2 a_texcoord0; 9 | varying vec2 v_texcoord0; 10 | void main(void) { 11 | vec4 pos = u_modelViewMatrix * vec4(a_position,1.0); 12 | v_normal = u_normalMatrix * a_normal; 13 | v_texcoord0 = a_texcoord0; 14 | gl_Position = u_projectionMatrix * pos; 15 | } 16 | -------------------------------------------------------------------------------- /specs/data/2.0/box-techniques-separate/Image0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/2.0/box-techniques-separate/Image0001.png -------------------------------------------------------------------------------- /specs/data/2.0/box-techniques-separate/box-techniques-separate.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "accessors": [ 3 | { 4 | "bufferView": 2, 5 | "byteOffset": 0, 6 | "componentType": 5123, 7 | "count": 36, 8 | "type": "SCALAR", 9 | "name": "accessor_indices", 10 | "min": [ 11 | 0 12 | ], 13 | "max": [ 14 | 23 15 | ] 16 | }, 17 | { 18 | "bufferView": 0, 19 | "byteOffset": 0, 20 | "componentType": 5126, 21 | "count": 24, 22 | "max": [ 23 | 0.5, 24 | 0.5, 25 | 0.5 26 | ], 27 | "min": [ 28 | -0.5, 29 | -0.5, 30 | -0.5 31 | ], 32 | "type": "VEC3", 33 | "name": "accessor_positions" 34 | }, 35 | { 36 | "bufferView": 0, 37 | "byteOffset": 288, 38 | "componentType": 5126, 39 | "count": 24, 40 | "max": [ 41 | 1, 42 | 1, 43 | 1 44 | ], 45 | "min": [ 46 | -1, 47 | -1, 48 | -1 49 | ], 50 | "type": "VEC3", 51 | "name": "accessor_normals" 52 | }, 53 | { 54 | "bufferView": 1, 55 | "byteOffset": 0, 56 | "componentType": 5126, 57 | "count": 24, 58 | "max": [ 59 | 6, 60 | 1 61 | ], 62 | "min": [ 63 | 0, 64 | 0 65 | ], 66 | "type": "VEC2", 67 | "name": "accessor_texcoord" 68 | } 69 | ], 70 | "asset": { 71 | "generator": "collada2gltf@ceec062e3d5793f2f249f53cbd843aee382ad40b", 72 | "version": "2.0" 73 | }, 74 | "bufferViews": [ 75 | { 76 | "buffer": 0, 77 | "byteLength": 576, 78 | "byteOffset": 0, 79 | "target": 34962, 80 | "name": "bufferView_0", 81 | "byteStride": 12 82 | }, 83 | { 84 | "buffer": 0, 85 | "byteLength": 192, 86 | "byteOffset": 576, 87 | "target": 34962, 88 | "name": "bufferView_0", 89 | "byteStride": 8 90 | }, 91 | { 92 | "buffer": 0, 93 | "byteLength": 72, 94 | "byteOffset": 768, 95 | "target": 34963, 96 | "name": "bufferView_1" 97 | } 98 | ], 99 | "buffers": [ 100 | { 101 | "name": "CesiumTexturedBoxTest", 102 | "byteLength": 840, 103 | "uri": "CesiumTexturedBoxTest.bin" 104 | } 105 | ], 106 | "images": [ 107 | { 108 | "name": "Image0001", 109 | "uri": "Image0001.png" 110 | } 111 | ], 112 | "materials": [ 113 | { 114 | "name": "Texture", 115 | "extensions": { 116 | "KHR_techniques_webgl": { 117 | "technique": 0, 118 | "values": { 119 | "u_diffuse": { 120 | "index": 0, 121 | "texCoord": 0 122 | }, 123 | "u_shininess": 256, 124 | "u_specular": [ 125 | 0.2, 126 | 0.2, 127 | 0.2, 128 | 1 129 | ] 130 | } 131 | } 132 | }, 133 | "emissiveFactor": [ 134 | 0, 135 | 0, 136 | 0 137 | ], 138 | "alphaMode": "OPAQUE", 139 | "doubleSided": false 140 | } 141 | ], 142 | "meshes": [ 143 | { 144 | "name": "Mesh", 145 | "primitives": [ 146 | { 147 | "attributes": { 148 | "NORMAL": 2, 149 | "POSITION": 1, 150 | "TEXCOORD_0": 3 151 | }, 152 | "indices": 0, 153 | "material": 0, 154 | "mode": 4 155 | } 156 | ] 157 | } 158 | ], 159 | "nodes": [ 160 | { 161 | "name": "rootNode", 162 | "mesh": 0 163 | } 164 | ], 165 | "samplers": [ 166 | { 167 | "magFilter": 9729, 168 | "minFilter": 9987, 169 | "wrapS": 10497, 170 | "wrapT": 10497, 171 | "name": "sampler_0" 172 | } 173 | ], 174 | "scene": 0, 175 | "scenes": [ 176 | { 177 | "nodes": [ 178 | 0 179 | ], 180 | "name": "defaultScene" 181 | } 182 | ], 183 | "textures": [ 184 | { 185 | "sampler": 0, 186 | "source": 0, 187 | "name": "texture_Image0001" 188 | } 189 | ], 190 | "extensionsUsed": [ 191 | "KHR_techniques_webgl" 192 | ], 193 | "extensionsRequired": [ 194 | "KHR_techniques_webgl" 195 | ], 196 | "extensions": { 197 | "KHR_techniques_webgl": { 198 | "programs": [ 199 | { 200 | "name": "program_0", 201 | "fragmentShader": 0, 202 | "vertexShader": 1 203 | } 204 | ], 205 | "shaders": [ 206 | { 207 | "type": 35632, 208 | "name": "CesiumTexturedBoxTest0FS", 209 | "uri": "CesiumTexturedBoxTest0FS.glsl" 210 | }, 211 | { 212 | "type": 35633, 213 | "name": "CesiumTexturedBoxTest0VS", 214 | "uri": "CesiumTexturedBoxTest0VS.glsl" 215 | } 216 | ], 217 | "techniques": [ 218 | { 219 | "name": "technique0", 220 | "program": 0, 221 | "attributes": { 222 | "a_normal": { 223 | "semantic": "NORMAL" 224 | }, 225 | "a_position": { 226 | "semantic": "POSITION" 227 | }, 228 | "a_texcoord0": { 229 | "semantic": "TEXCOORD_0" 230 | } 231 | }, 232 | "uniforms": { 233 | "u_diffuse": { 234 | "type": 35678 235 | }, 236 | "u_modelViewMatrix": { 237 | "type": 35676, 238 | "semantic": "MODELVIEW" 239 | }, 240 | "u_normalMatrix": { 241 | "type": 35675, 242 | "semantic": "MODELVIEWINVERSETRANSPOSE" 243 | }, 244 | "u_projectionMatrix": { 245 | "type": 35676, 246 | "semantic": "PROJECTION" 247 | }, 248 | "u_shininess": { 249 | "type": 5126 250 | }, 251 | "u_specular": { 252 | "type": 35666 253 | } 254 | } 255 | } 256 | ] 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /specs/data/2.0/box-textured-binary-separate/box-textured-binary-separate.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/2.0/box-textured-binary-separate/box-textured-binary-separate.glb -------------------------------------------------------------------------------- /specs/data/2.0/box-textured-binary-separate/cesium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/2.0/box-textured-binary-separate/cesium.png -------------------------------------------------------------------------------- /specs/data/2.0/box-textured-binary/box-textured-binary.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/2.0/box-textured-binary/box-textured-binary.glb -------------------------------------------------------------------------------- /specs/data/2.0/box-textured-separate/box-textured-separate.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/2.0/box-textured-separate/box-textured-separate.bin -------------------------------------------------------------------------------- /specs/data/2.0/box-textured-separate/box-textured-separate.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset": { 3 | "generator": "COLLADA2GLTF", 4 | "version": "2.0" 5 | }, 6 | "scene": 0, 7 | "scenes": [ 8 | { 9 | "nodes": [ 10 | 0 11 | ] 12 | } 13 | ], 14 | "nodes": [ 15 | { 16 | "children": [ 17 | 1 18 | ], 19 | "matrix": [ 20 | 1, 21 | 0, 22 | 0, 23 | 0, 24 | 0, 25 | 0, 26 | -1, 27 | 0, 28 | 0, 29 | 1, 30 | 0, 31 | 0, 32 | 0, 33 | 0, 34 | 0, 35 | 1 36 | ] 37 | }, 38 | { 39 | "mesh": 0 40 | } 41 | ], 42 | "meshes": [ 43 | { 44 | "primitives": [ 45 | { 46 | "attributes": { 47 | "NORMAL": 1, 48 | "POSITION": 2, 49 | "TEXCOORD_0": 3 50 | }, 51 | "indices": 0, 52 | "mode": 4, 53 | "material": 0 54 | } 55 | ], 56 | "name": "Mesh" 57 | } 58 | ], 59 | "accessors": [ 60 | { 61 | "bufferView": 0, 62 | "byteOffset": 0, 63 | "componentType": 5123, 64 | "count": 36, 65 | "max": [ 66 | 23 67 | ], 68 | "min": [ 69 | 0 70 | ], 71 | "type": "SCALAR" 72 | }, 73 | { 74 | "bufferView": 1, 75 | "byteOffset": 0, 76 | "componentType": 5126, 77 | "count": 24, 78 | "max": [ 79 | 1, 80 | 1, 81 | 1 82 | ], 83 | "min": [ 84 | -1, 85 | -1, 86 | -1 87 | ], 88 | "type": "VEC3" 89 | }, 90 | { 91 | "bufferView": 1, 92 | "byteOffset": 288, 93 | "componentType": 5126, 94 | "count": 24, 95 | "max": [ 96 | 0.5, 97 | 0.5, 98 | 0.5 99 | ], 100 | "min": [ 101 | -0.5, 102 | -0.5, 103 | -0.5 104 | ], 105 | "type": "VEC3" 106 | }, 107 | { 108 | "bufferView": 2, 109 | "byteOffset": 0, 110 | "componentType": 5126, 111 | "count": 24, 112 | "max": [ 113 | 6, 114 | 1 115 | ], 116 | "min": [ 117 | 0, 118 | 0 119 | ], 120 | "type": "VEC2" 121 | } 122 | ], 123 | "materials": [ 124 | { 125 | "pbrMetallicRoughness": { 126 | "baseColorTexture": { 127 | "index": 0, 128 | "texCoord": 0 129 | }, 130 | "metallicFactor": 0, 131 | "baseColorFactor": [ 132 | 1, 133 | 1, 134 | 1, 135 | 1 136 | ], 137 | "roughnessFactor": 1 138 | }, 139 | "name": "Texture", 140 | "emissiveFactor": [ 141 | 0, 142 | 0, 143 | 0 144 | ], 145 | "alphaMode": "OPAQUE", 146 | "doubleSided": false 147 | } 148 | ], 149 | "textures": [ 150 | { 151 | "sampler": 0, 152 | "source": 0 153 | } 154 | ], 155 | "images": [ 156 | { 157 | "uri": "cesium%20logo.png" 158 | } 159 | ], 160 | "samplers": [ 161 | { 162 | "magFilter": 9729, 163 | "minFilter": 9986, 164 | "wrapS": 10497, 165 | "wrapT": 10497 166 | } 167 | ], 168 | "bufferViews": [ 169 | { 170 | "buffer": 0, 171 | "byteOffset": 768, 172 | "byteLength": 72, 173 | "target": 34963 174 | }, 175 | { 176 | "buffer": 0, 177 | "byteOffset": 0, 178 | "byteLength": 576, 179 | "byteStride": 12, 180 | "target": 34962 181 | }, 182 | { 183 | "buffer": 0, 184 | "byteOffset": 576, 185 | "byteLength": 192, 186 | "byteStride": 8, 187 | "target": 34962 188 | } 189 | ], 190 | "buffers": [ 191 | { 192 | "byteLength": 840, 193 | "uri": "box-textured-separate.bin" 194 | } 195 | ] 196 | } 197 | -------------------------------------------------------------------------------- /specs/data/2.0/box-textured-separate/cesium logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/2.0/box-textured-separate/cesium logo.png -------------------------------------------------------------------------------- /specs/data/2.0/extensions/EXT_meshopt_compression/meshopt-fallback/meshopt-fallback.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/2.0/extensions/EXT_meshopt_compression/meshopt-fallback/meshopt-fallback.bin -------------------------------------------------------------------------------- /specs/data/2.0/extensions/EXT_meshopt_compression/meshopt-fallback/meshopt-fallback.fallback.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/2.0/extensions/EXT_meshopt_compression/meshopt-fallback/meshopt-fallback.fallback.bin -------------------------------------------------------------------------------- /specs/data/2.0/extensions/EXT_meshopt_compression/meshopt-fallback/meshopt-fallback.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "buffers": [ 3 | { 4 | "uri": "meshopt-fallback.bin", 5 | "byteLength": 468 6 | }, 7 | { 8 | "uri": "meshopt-fallback.fallback.bin", 9 | "byteLength": 960, 10 | "extensions": { 11 | "EXT_meshopt_compression": { 12 | "fallback": true 13 | } 14 | } 15 | } 16 | ], 17 | "asset": { 18 | "version": "2.0", 19 | "generator": "gltfpack 0.16" 20 | }, 21 | "extensionsUsed": [ 22 | "KHR_mesh_quantization", 23 | "EXT_meshopt_compression" 24 | ], 25 | "extensionsRequired": [ 26 | "KHR_mesh_quantization" 27 | ], 28 | "bufferViews": [ 29 | { 30 | "buffer": 1, 31 | "byteOffset": 0, 32 | "byteLength": 400, 33 | "byteStride": 8, 34 | "target": 34962, 35 | "extensions": { 36 | "EXT_meshopt_compression": { 37 | "buffer": 0, 38 | "byteOffset": 0, 39 | "byteLength": 265, 40 | "byteStride": 8, 41 | "mode": "ATTRIBUTES", 42 | "count": 50 43 | } 44 | } 45 | }, 46 | { 47 | "buffer": 1, 48 | "byteOffset": 400, 49 | "byteLength": 200, 50 | "byteStride": 4, 51 | "target": 34962, 52 | "extensions": { 53 | "EXT_meshopt_compression": { 54 | "buffer": 0, 55 | "byteOffset": 268, 56 | "byteLength": 117, 57 | "byteStride": 4, 58 | "mode": "ATTRIBUTES", 59 | "filter": "OCTAHEDRAL", 60 | "count": 50 61 | } 62 | } 63 | }, 64 | { 65 | "buffer": 1, 66 | "byteOffset": 600, 67 | "byteLength": 360, 68 | "target": 34963, 69 | "extensions": { 70 | "EXT_meshopt_compression": { 71 | "buffer": 0, 72 | "byteOffset": 388, 73 | "byteLength": 78, 74 | "byteStride": 2, 75 | "mode": "TRIANGLES", 76 | "count": 180 77 | } 78 | } 79 | } 80 | ], 81 | "accessors": [ 82 | { 83 | "bufferView": 0, 84 | "byteOffset": 0, 85 | "componentType": 5123, 86 | "count": 24, 87 | "type": "VEC3", 88 | "min": [ 89 | 0, 90 | 0, 91 | 0 92 | ], 93 | "max": [ 94 | 16383, 95 | 16383, 96 | 16383 97 | ] 98 | }, 99 | { 100 | "bufferView": 1, 101 | "byteOffset": 0, 102 | "componentType": 5120, 103 | "count": 24, 104 | "type": "VEC3", 105 | "normalized": true 106 | }, 107 | { 108 | "bufferView": 2, 109 | "byteOffset": 0, 110 | "componentType": 5123, 111 | "count": 36, 112 | "type": "SCALAR" 113 | }, 114 | { 115 | "bufferView": 0, 116 | "byteOffset": 192, 117 | "componentType": 5123, 118 | "count": 26, 119 | "type": "VEC3", 120 | "min": [ 121 | 1315, 122 | 1315, 123 | 1315 124 | ], 125 | "max": [ 126 | 15068, 127 | 15068, 128 | 15068 129 | ] 130 | }, 131 | { 132 | "bufferView": 1, 133 | "byteOffset": 96, 134 | "componentType": 5120, 135 | "count": 26, 136 | "type": "VEC3", 137 | "normalized": true 138 | }, 139 | { 140 | "bufferView": 2, 141 | "byteOffset": 72, 142 | "componentType": 5123, 143 | "count": 144, 144 | "type": "SCALAR" 145 | } 146 | ], 147 | "materials": [ 148 | { 149 | "name": "Material.002", 150 | "pbrMetallicRoughness": { 151 | "baseColorFactor": [ 152 | 0.800000072, 153 | 0.00122899958, 154 | 0, 155 | 1 156 | ], 157 | "metallicFactor": 0, 158 | "roughnessFactor": 0.5 159 | }, 160 | "doubleSided": true 161 | }, 162 | { 163 | "name": "Material.001", 164 | "pbrMetallicRoughness": { 165 | "baseColorFactor": [ 166 | 0.800000012, 167 | 0.800000012, 168 | 0.800000012, 169 | 1 170 | ], 171 | "metallicFactor": 0, 172 | "roughnessFactor": 0.5 173 | }, 174 | "doubleSided": true 175 | } 176 | ], 177 | "meshes": [ 178 | { 179 | "primitives": [ 180 | { 181 | "attributes": { 182 | "POSITION": 0, 183 | "NORMAL": 1 184 | }, 185 | "mode": 4, 186 | "indices": 2, 187 | "material": 0 188 | } 189 | ] 190 | }, 191 | { 192 | "primitives": [ 193 | { 194 | "attributes": { 195 | "POSITION": 3, 196 | "NORMAL": 4 197 | }, 198 | "mode": 4, 199 | "indices": 5, 200 | "material": 1 201 | } 202 | ] 203 | } 204 | ], 205 | "nodes": [ 206 | { 207 | "mesh": 0, 208 | "translation": [ 209 | -1, 210 | -1, 211 | -1 212 | ], 213 | "scale": [ 214 | 0.000122077763, 215 | 0.000122077763, 216 | 0.000122077763 217 | ] 218 | }, 219 | { 220 | "mesh": 1, 221 | "translation": [ 222 | -1, 223 | -1, 224 | -1 225 | ], 226 | "scale": [ 227 | 0.000122077763, 228 | 0.000122077763, 229 | 0.000122077763 230 | ] 231 | }, 232 | { 233 | "name": "Cube", 234 | "children": [ 235 | 0 236 | ] 237 | }, 238 | { 239 | "name": "Cube.001", 240 | "translation": [ 241 | 0, 242 | 2.87217259, 243 | 0 244 | ], 245 | "children": [ 246 | 1 247 | ] 248 | } 249 | ], 250 | "scenes": [ 251 | { 252 | "name": "Scene", 253 | "nodes": [ 254 | 2, 255 | 3 256 | ] 257 | } 258 | ], 259 | "scene": 0 260 | } 261 | -------------------------------------------------------------------------------- /specs/data/2.0/extensions/EXT_meshopt_compression/meshopt-no-fallback/meshopt-no-fallback.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/2.0/extensions/EXT_meshopt_compression/meshopt-no-fallback/meshopt-no-fallback.bin -------------------------------------------------------------------------------- /specs/data/2.0/extensions/EXT_meshopt_compression/meshopt-no-fallback/meshopt-no-fallback.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "buffers": [ 3 | { 4 | "uri": "meshopt-no-fallback.bin", 5 | "byteLength": 468 6 | }, 7 | { 8 | "byteLength": 960 9 | } 10 | ], 11 | "asset": { 12 | "version": "2.0", 13 | "generator": "gltfpack 0.16" 14 | }, 15 | "extensionsUsed": [ 16 | "KHR_mesh_quantization", 17 | "EXT_meshopt_compression" 18 | ], 19 | "extensionsRequired": [ 20 | "KHR_mesh_quantization", 21 | "EXT_meshopt_compression" 22 | ], 23 | "bufferViews": [ 24 | { 25 | "buffer": 1, 26 | "byteOffset": 0, 27 | "byteLength": 400, 28 | "byteStride": 8, 29 | "target": 34962, 30 | "extensions": { 31 | "EXT_meshopt_compression": { 32 | "buffer": 0, 33 | "byteOffset": 0, 34 | "byteLength": 265, 35 | "byteStride": 8, 36 | "mode": "ATTRIBUTES", 37 | "count": 50 38 | } 39 | } 40 | }, 41 | { 42 | "buffer": 1, 43 | "byteOffset": 400, 44 | "byteLength": 200, 45 | "byteStride": 4, 46 | "target": 34962, 47 | "extensions": { 48 | "EXT_meshopt_compression": { 49 | "buffer": 0, 50 | "byteOffset": 268, 51 | "byteLength": 117, 52 | "byteStride": 4, 53 | "mode": "ATTRIBUTES", 54 | "filter": "OCTAHEDRAL", 55 | "count": 50 56 | } 57 | } 58 | }, 59 | { 60 | "buffer": 1, 61 | "byteOffset": 600, 62 | "byteLength": 360, 63 | "target": 34963, 64 | "extensions": { 65 | "EXT_meshopt_compression": { 66 | "buffer": 0, 67 | "byteOffset": 388, 68 | "byteLength": 78, 69 | "byteStride": 2, 70 | "mode": "TRIANGLES", 71 | "count": 180 72 | } 73 | } 74 | } 75 | ], 76 | "accessors": [ 77 | { 78 | "bufferView": 0, 79 | "byteOffset": 0, 80 | "componentType": 5123, 81 | "count": 24, 82 | "type": "VEC3", 83 | "min": [ 84 | 0, 85 | 0, 86 | 0 87 | ], 88 | "max": [ 89 | 16383, 90 | 16383, 91 | 16383 92 | ] 93 | }, 94 | { 95 | "bufferView": 1, 96 | "byteOffset": 0, 97 | "componentType": 5120, 98 | "count": 24, 99 | "type": "VEC3", 100 | "normalized": true 101 | }, 102 | { 103 | "bufferView": 2, 104 | "byteOffset": 0, 105 | "componentType": 5123, 106 | "count": 36, 107 | "type": "SCALAR" 108 | }, 109 | { 110 | "bufferView": 0, 111 | "byteOffset": 192, 112 | "componentType": 5123, 113 | "count": 26, 114 | "type": "VEC3", 115 | "min": [ 116 | 1315, 117 | 1315, 118 | 1315 119 | ], 120 | "max": [ 121 | 15068, 122 | 15068, 123 | 15068 124 | ] 125 | }, 126 | { 127 | "bufferView": 1, 128 | "byteOffset": 96, 129 | "componentType": 5120, 130 | "count": 26, 131 | "type": "VEC3", 132 | "normalized": true 133 | }, 134 | { 135 | "bufferView": 2, 136 | "byteOffset": 72, 137 | "componentType": 5123, 138 | "count": 144, 139 | "type": "SCALAR" 140 | } 141 | ], 142 | "materials": [ 143 | { 144 | "name": "Material.002", 145 | "pbrMetallicRoughness": { 146 | "baseColorFactor": [ 147 | 0.800000072, 148 | 0.00122899958, 149 | 0, 150 | 1 151 | ], 152 | "metallicFactor": 0, 153 | "roughnessFactor": 0.5 154 | }, 155 | "doubleSided": true 156 | }, 157 | { 158 | "name": "Material.001", 159 | "pbrMetallicRoughness": { 160 | "baseColorFactor": [ 161 | 0.800000012, 162 | 0.800000012, 163 | 0.800000012, 164 | 1 165 | ], 166 | "metallicFactor": 0, 167 | "roughnessFactor": 0.5 168 | }, 169 | "doubleSided": true 170 | } 171 | ], 172 | "meshes": [ 173 | { 174 | "primitives": [ 175 | { 176 | "attributes": { 177 | "POSITION": 0, 178 | "NORMAL": 1 179 | }, 180 | "mode": 4, 181 | "indices": 2, 182 | "material": 0 183 | } 184 | ] 185 | }, 186 | { 187 | "primitives": [ 188 | { 189 | "attributes": { 190 | "POSITION": 3, 191 | "NORMAL": 4 192 | }, 193 | "mode": 4, 194 | "indices": 5, 195 | "material": 1 196 | } 197 | ] 198 | } 199 | ], 200 | "nodes": [ 201 | { 202 | "mesh": 0, 203 | "translation": [ 204 | -1, 205 | -1, 206 | -1 207 | ], 208 | "scale": [ 209 | 0.000122077763, 210 | 0.000122077763, 211 | 0.000122077763 212 | ] 213 | }, 214 | { 215 | "mesh": 1, 216 | "translation": [ 217 | -1, 218 | -1, 219 | -1 220 | ], 221 | "scale": [ 222 | 0.000122077763, 223 | 0.000122077763, 224 | 0.000122077763 225 | ] 226 | }, 227 | { 228 | "name": "Cube", 229 | "children": [ 230 | 0 231 | ] 232 | }, 233 | { 234 | "name": "Cube.001", 235 | "translation": [ 236 | 0, 237 | 2.87217259, 238 | 0 239 | ], 240 | "children": [ 241 | 1 242 | ] 243 | } 244 | ], 245 | "scenes": [ 246 | { 247 | "name": "Scene", 248 | "nodes": [ 249 | 2, 250 | 3 251 | ] 252 | } 253 | ], 254 | "scene": 0 255 | } 256 | -------------------------------------------------------------------------------- /specs/data/2.0/extensions/EXT_texture_webp/box-textured-separate/box-textured-separate.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/2.0/extensions/EXT_texture_webp/box-textured-separate/box-textured-separate.bin -------------------------------------------------------------------------------- /specs/data/2.0/extensions/EXT_texture_webp/box-textured-separate/box-textured-separate.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset": { 3 | "generator": "COLLADA2GLTF", 4 | "version": "2.0" 5 | }, 6 | "extensionsUsed": [ 7 | "EXT_texture_webp" 8 | ], 9 | "extensionsRequired": [ 10 | "EXT_texture_webp" 11 | ], 12 | "scene": 0, 13 | "scenes": [ 14 | { 15 | "nodes": [ 16 | 0 17 | ] 18 | } 19 | ], 20 | "nodes": [ 21 | { 22 | "children": [ 23 | 1 24 | ], 25 | "matrix": [ 26 | 1, 27 | 0, 28 | 0, 29 | 0, 30 | 0, 31 | 0, 32 | -1, 33 | 0, 34 | 0, 35 | 1, 36 | 0, 37 | 0, 38 | 0, 39 | 0, 40 | 0, 41 | 1 42 | ] 43 | }, 44 | { 45 | "mesh": 0 46 | } 47 | ], 48 | "meshes": [ 49 | { 50 | "primitives": [ 51 | { 52 | "attributes": { 53 | "NORMAL": 1, 54 | "POSITION": 2, 55 | "TEXCOORD_0": 3 56 | }, 57 | "indices": 0, 58 | "mode": 4, 59 | "material": 0 60 | } 61 | ], 62 | "name": "Mesh" 63 | } 64 | ], 65 | "accessors": [ 66 | { 67 | "bufferView": 0, 68 | "byteOffset": 0, 69 | "componentType": 5123, 70 | "count": 36, 71 | "max": [ 72 | 23 73 | ], 74 | "min": [ 75 | 0 76 | ], 77 | "type": "SCALAR" 78 | }, 79 | { 80 | "bufferView": 1, 81 | "byteOffset": 0, 82 | "componentType": 5126, 83 | "count": 24, 84 | "max": [ 85 | 1, 86 | 1, 87 | 1 88 | ], 89 | "min": [ 90 | -1, 91 | -1, 92 | -1 93 | ], 94 | "type": "VEC3" 95 | }, 96 | { 97 | "bufferView": 1, 98 | "byteOffset": 288, 99 | "componentType": 5126, 100 | "count": 24, 101 | "max": [ 102 | 0.5, 103 | 0.5, 104 | 0.5 105 | ], 106 | "min": [ 107 | -0.5, 108 | -0.5, 109 | -0.5 110 | ], 111 | "type": "VEC3" 112 | }, 113 | { 114 | "bufferView": 2, 115 | "byteOffset": 0, 116 | "componentType": 5126, 117 | "count": 24, 118 | "max": [ 119 | 6, 120 | 1 121 | ], 122 | "min": [ 123 | 0, 124 | 0 125 | ], 126 | "type": "VEC2" 127 | } 128 | ], 129 | "materials": [ 130 | { 131 | "pbrMetallicRoughness": { 132 | "baseColorTexture": { 133 | "index": 0, 134 | "texCoord": 0 135 | }, 136 | "metallicFactor": 0, 137 | "baseColorFactor": [ 138 | 1, 139 | 1, 140 | 1, 141 | 1 142 | ], 143 | "roughnessFactor": 1 144 | }, 145 | "name": "Texture", 146 | "emissiveFactor": [ 147 | 0, 148 | 0, 149 | 0 150 | ], 151 | "alphaMode": "OPAQUE", 152 | "doubleSided": false 153 | } 154 | ], 155 | "textures": [ 156 | { 157 | "sampler": 0, 158 | "extensions": { 159 | "EXT_texture_webp": { 160 | "source": 0 161 | } 162 | } 163 | } 164 | ], 165 | "images": [ 166 | { 167 | "uri": "cesium%20logo.webp" 168 | } 169 | ], 170 | "samplers": [ 171 | { 172 | "magFilter": 9729, 173 | "minFilter": 9986, 174 | "wrapS": 10497, 175 | "wrapT": 10497 176 | } 177 | ], 178 | "bufferViews": [ 179 | { 180 | "buffer": 0, 181 | "byteOffset": 768, 182 | "byteLength": 72, 183 | "target": 34963 184 | }, 185 | { 186 | "buffer": 0, 187 | "byteOffset": 0, 188 | "byteLength": 576, 189 | "byteStride": 12, 190 | "target": 34962 191 | }, 192 | { 193 | "buffer": 0, 194 | "byteOffset": 576, 195 | "byteLength": 192, 196 | "byteStride": 8, 197 | "target": 34962 198 | } 199 | ], 200 | "buffers": [ 201 | { 202 | "byteLength": 840, 203 | "uri": "box-textured-separate.bin" 204 | } 205 | ] 206 | } 207 | -------------------------------------------------------------------------------- /specs/data/2.0/extensions/EXT_texture_webp/box-textured-separate/box-textured-with-fallback.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset": { 3 | "generator": "COLLADA2GLTF", 4 | "version": "2.0" 5 | }, 6 | "extensionsUsed": [ 7 | "EXT_texture_webp" 8 | ], 9 | "scene": 0, 10 | "scenes": [ 11 | { 12 | "nodes": [ 13 | 0 14 | ] 15 | } 16 | ], 17 | "nodes": [ 18 | { 19 | "children": [ 20 | 1 21 | ], 22 | "matrix": [ 23 | 1, 24 | 0, 25 | 0, 26 | 0, 27 | 0, 28 | 0, 29 | -1, 30 | 0, 31 | 0, 32 | 1, 33 | 0, 34 | 0, 35 | 0, 36 | 0, 37 | 0, 38 | 1 39 | ] 40 | }, 41 | { 42 | "mesh": 0 43 | } 44 | ], 45 | "meshes": [ 46 | { 47 | "primitives": [ 48 | { 49 | "attributes": { 50 | "NORMAL": 1, 51 | "POSITION": 2, 52 | "TEXCOORD_0": 3 53 | }, 54 | "indices": 0, 55 | "mode": 4, 56 | "material": 0 57 | } 58 | ], 59 | "name": "Mesh" 60 | } 61 | ], 62 | "accessors": [ 63 | { 64 | "bufferView": 0, 65 | "byteOffset": 0, 66 | "componentType": 5123, 67 | "count": 36, 68 | "max": [ 69 | 23 70 | ], 71 | "min": [ 72 | 0 73 | ], 74 | "type": "SCALAR" 75 | }, 76 | { 77 | "bufferView": 1, 78 | "byteOffset": 0, 79 | "componentType": 5126, 80 | "count": 24, 81 | "max": [ 82 | 1, 83 | 1, 84 | 1 85 | ], 86 | "min": [ 87 | -1, 88 | -1, 89 | -1 90 | ], 91 | "type": "VEC3" 92 | }, 93 | { 94 | "bufferView": 1, 95 | "byteOffset": 288, 96 | "componentType": 5126, 97 | "count": 24, 98 | "max": [ 99 | 0.5, 100 | 0.5, 101 | 0.5 102 | ], 103 | "min": [ 104 | -0.5, 105 | -0.5, 106 | -0.5 107 | ], 108 | "type": "VEC3" 109 | }, 110 | { 111 | "bufferView": 2, 112 | "byteOffset": 0, 113 | "componentType": 5126, 114 | "count": 24, 115 | "max": [ 116 | 6, 117 | 1 118 | ], 119 | "min": [ 120 | 0, 121 | 0 122 | ], 123 | "type": "VEC2" 124 | } 125 | ], 126 | "materials": [ 127 | { 128 | "pbrMetallicRoughness": { 129 | "baseColorTexture": { 130 | "index": 0, 131 | "texCoord": 0 132 | }, 133 | "metallicFactor": 0, 134 | "baseColorFactor": [ 135 | 1, 136 | 1, 137 | 1, 138 | 1 139 | ], 140 | "roughnessFactor": 1 141 | }, 142 | "name": "Texture", 143 | "emissiveFactor": [ 144 | 0, 145 | 0, 146 | 0 147 | ], 148 | "alphaMode": "OPAQUE", 149 | "doubleSided": false 150 | } 151 | ], 152 | "textures": [ 153 | { 154 | "sampler": 0, 155 | "source": 1, 156 | "extensions": { 157 | "EXT_texture_webp": { 158 | "source": 0 159 | } 160 | } 161 | } 162 | ], 163 | "images": [ 164 | { 165 | "uri": "cesium%20logo.webp" 166 | }, 167 | { 168 | "uri": "cesium%20logo.png" 169 | } 170 | ], 171 | "samplers": [ 172 | { 173 | "magFilter": 9729, 174 | "minFilter": 9986, 175 | "wrapS": 10497, 176 | "wrapT": 10497 177 | } 178 | ], 179 | "bufferViews": [ 180 | { 181 | "buffer": 0, 182 | "byteOffset": 768, 183 | "byteLength": 72, 184 | "target": 34963 185 | }, 186 | { 187 | "buffer": 0, 188 | "byteOffset": 0, 189 | "byteLength": 576, 190 | "byteStride": 12, 191 | "target": 34962 192 | }, 193 | { 194 | "buffer": 0, 195 | "byteOffset": 576, 196 | "byteLength": 192, 197 | "byteStride": 8, 198 | "target": 34962 199 | } 200 | ], 201 | "buffers": [ 202 | { 203 | "byteLength": 840, 204 | "uri": "box-textured-separate.bin" 205 | } 206 | ] 207 | } 208 | -------------------------------------------------------------------------------- /specs/data/2.0/extensions/EXT_texture_webp/box-textured-separate/cesium logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/2.0/extensions/EXT_texture_webp/box-textured-separate/cesium logo.png -------------------------------------------------------------------------------- /specs/data/2.0/extensions/EXT_texture_webp/box-textured-separate/cesium logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CesiumGS/gltf-pipeline/cbbff2f212b5aa6163c7152d46ffcaf3a2ff3ac3/specs/data/2.0/extensions/EXT_texture_webp/box-textured-separate/cesium logo.webp -------------------------------------------------------------------------------- /specs/data/2.0/triangle-without-indices/triangle-without-indices.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "scenes" : [ 3 | { 4 | "nodes" : [ 0 ] 5 | } 6 | ], 7 | 8 | "nodes" : [ 9 | { 10 | "mesh" : 0 11 | } 12 | ], 13 | 14 | "meshes" : [ 15 | { 16 | "primitives" : [ { 17 | "attributes" : { 18 | "POSITION" : 0 19 | } 20 | } ] 21 | } 22 | ], 23 | 24 | "buffers" : [ 25 | { 26 | "uri" : "data:application/octet-stream;base64,AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAA", 27 | "byteLength" : 36 28 | } 29 | ], 30 | "bufferViews" : [ 31 | { 32 | "buffer" : 0, 33 | "byteOffset" : 0, 34 | "byteLength" : 36, 35 | "target" : 34962 36 | } 37 | ], 38 | "accessors" : [ 39 | { 40 | "bufferView" : 0, 41 | "byteOffset" : 0, 42 | "componentType" : 5126, 43 | "count" : 3, 44 | "type" : "VEC3", 45 | "max" : [ 1.0, 1.0, 0.0 ], 46 | "min" : [ 0.0, 0.0, 0.0 ] 47 | } 48 | ], 49 | 50 | "asset" : { 51 | "version" : "2.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /specs/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "specs", 3 | "spec_files": [ 4 | "**/*Spec.js" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /specs/lib/addBufferSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const addBuffer = require("../../lib/addBuffer"); 3 | 4 | describe("addBuffer", () => { 5 | it("adds buffer to gltf and returns its bufferView id", () => { 6 | const gltf = { 7 | buffers: [], 8 | bufferViews: [], 9 | }; 10 | const buffer0 = Buffer.alloc(100); 11 | const buffer1 = Buffer.alloc(200); 12 | 13 | expect(addBuffer(gltf, buffer0)).toBe(0); 14 | expect(addBuffer(gltf, buffer1)).toBe(1); 15 | expect(gltf.buffers.length).toBe(2); 16 | expect(gltf.bufferViews.length).toBe(2); 17 | expect(gltf.buffers[0].extras._pipeline.source).toEqual(buffer0); 18 | expect(gltf.buffers[1].extras._pipeline.source).toEqual(buffer1); 19 | expect(gltf.bufferViews[0].buffer).toBe(0); 20 | expect(gltf.bufferViews[1].buffer).toBe(1); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /specs/lib/addExtensionsRequiredSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const addExtensionsRequired = require("../../lib/addExtensionsRequired"); 3 | 4 | describe("addExtensionsRequired", () => { 5 | it("adds an extension to extensionsRequired", () => { 6 | const gltf = {}; 7 | addExtensionsRequired(gltf, "KHR_materials_pbrSpecularGlossiness"); 8 | addExtensionsRequired(gltf, "KHR_draco_mesh_compression"); 9 | addExtensionsRequired(gltf, "KHR_draco_mesh_compression"); // Test adding duplicate 10 | expect(gltf.extensionsRequired).toEqual([ 11 | "KHR_materials_pbrSpecularGlossiness", 12 | "KHR_draco_mesh_compression", 13 | ]); 14 | expect(gltf.extensionsUsed).toEqual([ 15 | "KHR_materials_pbrSpecularGlossiness", 16 | "KHR_draco_mesh_compression", 17 | ]); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /specs/lib/addExtensionsUsedSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const addExtensionsUsed = require("../../lib/addExtensionsUsed"); 3 | 4 | describe("addExtensionsUsed", () => { 5 | it("adds an extension to extensionsUsed", () => { 6 | const gltf = {}; 7 | addExtensionsUsed(gltf, "KHR_materials_pbrSpecularGlossiness"); 8 | addExtensionsUsed(gltf, "KHR_draco_mesh_compression"); 9 | addExtensionsUsed(gltf, "KHR_draco_mesh_compression"); // Test adding duplicate 10 | expect(gltf.extensionsUsed).toEqual([ 11 | "KHR_materials_pbrSpecularGlossiness", 12 | "KHR_draco_mesh_compression", 13 | ]); 14 | expect(gltf.extensionsRequired).toBeUndefined(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /specs/lib/addPipelineExtrasSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const addPipelineExtras = require("../../lib/addPipelineExtras"); 4 | 5 | const WebGLConstants = Cesium.WebGLConstants; 6 | 7 | describe("addPipelineExtras", () => { 8 | it("adds pipeline extras to glTF 1.0 assets", () => { 9 | const gltf = { 10 | buffers: { 11 | sampleBuffer0: { 12 | byteLength: 100, 13 | }, 14 | }, 15 | shaders: { 16 | sample0VS: { 17 | type: WebGLConstants.VERTEX_SHADER, 18 | uri: "data:,", 19 | }, 20 | }, 21 | }; 22 | const gltfWithExtras = addPipelineExtras(gltf); 23 | expect( 24 | gltfWithExtras.buffers["sampleBuffer0"].extras._pipeline, 25 | ).toBeDefined(); 26 | expect(gltfWithExtras.shaders["sample0VS"].extras._pipeline).toBeDefined(); 27 | }); 28 | 29 | it("adds pipeline extras to glTF 2.0 assets", () => { 30 | const gltf = { 31 | buffers: [ 32 | { 33 | byteLength: 100, 34 | }, 35 | ], 36 | extensions: { 37 | KHR_techniques_webgl: { 38 | shaders: [ 39 | { 40 | type: WebGLConstants.VERTEX_SHADER, 41 | uri: "data:,", 42 | }, 43 | ], 44 | }, 45 | }, 46 | extensionsRequired: ["KHR_techniques_webgl"], 47 | extensionsUsed: ["KHR_techniques_webgl"], 48 | }; 49 | const gltfWithExtras = addPipelineExtras(gltf); 50 | expect(gltfWithExtras.buffers[0].extras._pipeline).toBeDefined(); 51 | expect( 52 | gltfWithExtras.extensions.KHR_techniques_webgl.shaders[0].extras 53 | ._pipeline, 54 | ).toBeDefined(); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /specs/lib/addToArraySpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const addToArray = require("../../lib/addToArray"); 3 | 4 | describe("addToArray", () => { 5 | it("adds item to array and returns its index", () => { 6 | const gltf = { 7 | buffers: [], 8 | }; 9 | const buffer0 = { 10 | byteLength: 100, 11 | }; 12 | const buffer1 = { 13 | byteLength: 200, 14 | }; 15 | expect(addToArray(gltf.buffers, buffer0)).toBe(0); 16 | expect(addToArray(gltf.buffers, buffer1)).toBe(1); 17 | expect(gltf.buffers).toEqual([buffer0, buffer1]); 18 | }); 19 | 20 | it("returns index of duplicate element when checkDuplicates is true", () => { 21 | const gltf = { 22 | buffers: [], 23 | }; 24 | const buffer0 = { 25 | byteLength: 100, 26 | }; 27 | const buffer1 = { 28 | byteLength: 200, 29 | }; 30 | expect(addToArray(gltf.buffers, buffer0, true)).toBe(0); 31 | expect(addToArray(gltf.buffers, buffer1, true)).toBe(1); 32 | expect(addToArray(gltf.buffers, buffer0, true)).toBe(0); 33 | expect(gltf.buffers.length).toBe(2); 34 | expect(addToArray(gltf.buffers, buffer0)).toBe(2); 35 | expect(gltf.buffers.length).toBe(3); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /specs/lib/dataUriToBufferSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const dataUriToBuffer = require("../../lib/dataUriToBuffer"); 3 | 4 | describe("dataUriToBuffer", () => { 5 | it("converts base64 data uri to buffer", () => { 6 | const buffer = Buffer.from([103, 108, 84, 70]); 7 | const dataUri = `data:application/octet-stream;base64,${buffer.toString( 8 | "base64", 9 | )}`; 10 | expect(dataUriToBuffer(dataUri)).toEqual(buffer); 11 | }); 12 | 13 | it("converts utf8 data uri to buffer", () => { 14 | const buffer = Buffer.from([103, 108, 84, 70]); 15 | const dataUri = `data:text/plain;charset=utf-8,${buffer.toString("utf8")}`; 16 | expect(dataUriToBuffer(dataUri)).toEqual(buffer); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /specs/lib/findAccessorMinMaxSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const findAccessorMinMax = require("../../lib/findAccessorMinMax"); 3 | const readResources = require("../../lib/readResources"); 4 | 5 | const contiguousData = [ 6 | -1.0, -2.0, -3.0, 3.0, 2.0, 1.0, 0.0, 0.0, 0.0, 0.5, -0.5, 0.5, 7 | ]; 8 | 9 | const nan = Number.NaN; 10 | const nonContiguousData = [ 11 | -1.0, 12 | 1.0, 13 | -1.0, 14 | nan, 15 | nan, 16 | nan, 17 | 0.0, 18 | 0.0, 19 | 0.0, 20 | nan, 21 | nan, 22 | nan, 23 | 3.0, 24 | 2.0, 25 | 1.0, 26 | nan, 27 | nan, 28 | nan, 29 | -1.0, 30 | -2.0, 31 | -3.0, 32 | nan, 33 | nan, 34 | nan, 35 | ]; 36 | 37 | function createGltf(elements, byteStride) { 38 | const buffer = Buffer.from(new Float32Array(elements).buffer); 39 | const byteLength = buffer.length; 40 | const dataUri = `data:application/octet-stream;base64,${buffer.toString( 41 | "base64", 42 | )}`; 43 | const gltf = { 44 | asset: { 45 | version: "2.0", 46 | }, 47 | accessors: [ 48 | { 49 | bufferView: 0, 50 | byteOffset: 0, 51 | componentType: 5126, 52 | count: 4, 53 | type: "VEC3", 54 | }, 55 | ], 56 | bufferViews: [ 57 | { 58 | buffer: 0, 59 | byteOffset: 0, 60 | byteLength: byteLength, 61 | byteStride: byteStride, 62 | }, 63 | ], 64 | buffers: [ 65 | { 66 | uri: dataUri, 67 | byteLength: byteLength, 68 | }, 69 | ], 70 | }; 71 | return readResources(gltf); 72 | } 73 | 74 | describe("findAccessorMinMax", () => { 75 | it("finds the min and max of an accessor", async () => { 76 | const gltf = await createGltf(contiguousData, 12); 77 | const expectedMin = [-1.0, -2.0, -3.0]; 78 | const expectedMax = [3.0, 2.0, 1.0]; 79 | const minMax = findAccessorMinMax(gltf, gltf.accessors[0]); 80 | expect(minMax.min).toEqual(expectedMin); 81 | expect(minMax.max).toEqual(expectedMax); 82 | }); 83 | 84 | it("finds the min and max in a non-contiguous accessor", async () => { 85 | const gltf = await createGltf(nonContiguousData, 24); 86 | const expectedMin = [-1.0, -2.0, -3.0]; 87 | const expectedMax = [3.0, 2.0, 1.0]; 88 | const minMax = findAccessorMinMax(gltf, gltf.accessors[0]); 89 | expect(minMax.min).toEqual(expectedMin); 90 | expect(minMax.max).toEqual(expectedMax); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /specs/lib/getAccessorByteStrideSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const getAccessorByteStride = require("../../lib/getAccessorByteStride"); 4 | 5 | const WebGLConstants = Cesium.WebGLConstants; 6 | 7 | describe("getAccessorByteStride", () => { 8 | it("gets accessor byte stride", () => { 9 | const gltf = { 10 | accessors: [ 11 | { 12 | componentType: WebGLConstants.FLOAT, 13 | count: 24, 14 | type: "VEC3", 15 | min: [-1.0, -1.0, -1.0], 16 | max: [1.0, 1.0, 1.0], 17 | }, 18 | { 19 | bufferView: 0, 20 | componentType: WebGLConstants.FLOAT, 21 | count: 24, 22 | type: "VEC3", 23 | min: [-1.0, -1.0, -1.0], 24 | max: [1.0, 1.0, 1.0], 25 | }, 26 | { 27 | bufferView: 1, 28 | componentType: WebGLConstants.FLOAT, 29 | count: 24, 30 | type: "VEC3", 31 | min: [-1.0, -1.0, -1.0], 32 | max: [1.0, 1.0, 1.0], 33 | }, 34 | { 35 | componentType: WebGLConstants.FLOAT, 36 | count: 24, 37 | type: "VEC2", 38 | min: [0.0, 0.0], 39 | max: [1.0, 1.0], 40 | }, 41 | { 42 | componentType: WebGLConstants.INT, 43 | count: 36, 44 | type: "SCALAR", 45 | min: [0], 46 | max: [24], 47 | }, 48 | ], 49 | bufferViews: [ 50 | { 51 | buffer: 0, 52 | byteLength: 288, 53 | byteOffset: 0, 54 | }, 55 | { 56 | buffer: 0, 57 | byteLength: 288, 58 | byteOffset: 288, 59 | byteStride: 32, 60 | }, 61 | ], 62 | }; 63 | 64 | expect(getAccessorByteStride(gltf, gltf.accessors[0])).toBe(12); 65 | expect(getAccessorByteStride(gltf, gltf.accessors[1])).toBe(12); 66 | expect(getAccessorByteStride(gltf, gltf.accessors[2])).toBe(32); 67 | expect(getAccessorByteStride(gltf, gltf.accessors[3])).toBe(8); 68 | expect(getAccessorByteStride(gltf, gltf.accessors[4])).toBe(4); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /specs/lib/getBufferPaddedSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const getBufferPadded = require("../../lib/getBufferPadded"); 3 | 4 | describe("getBufferPadded", () => { 5 | it("gets buffer padded to 8 bytes", () => { 6 | let buffer = Buffer.alloc(0); 7 | let bufferPadded = getBufferPadded(buffer); 8 | expect(bufferPadded.length).toBe(0); 9 | 10 | buffer = Buffer.from([1]); 11 | bufferPadded = getBufferPadded(buffer); 12 | expect(bufferPadded.length).toBe(8); 13 | expect(bufferPadded.readUInt8(0)).toBe(1); 14 | expect(bufferPadded.readUInt8(1)).toBe(0); 15 | expect(bufferPadded.readUInt8(2)).toBe(0); 16 | expect(bufferPadded.readUInt8(3)).toBe(0); 17 | expect(bufferPadded.readUInt8(4)).toBe(0); 18 | expect(bufferPadded.readUInt8(5)).toBe(0); 19 | expect(bufferPadded.readUInt8(6)).toBe(0); 20 | expect(bufferPadded.readUInt8(7)).toBe(0); 21 | 22 | // Does not allocate a new buffer when buffer length is already aligned to 8 bytes 23 | buffer = Buffer.alloc(8); 24 | bufferPadded = getBufferPadded(buffer); 25 | expect(bufferPadded).toBe(buffer); 26 | 27 | buffer = Buffer.alloc(70); 28 | bufferPadded = getBufferPadded(buffer); 29 | expect(bufferPadded.length).toBe(72); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /specs/lib/getComponentReaderSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const getComponentReader = require("../../lib/getComponentReader"); 4 | 5 | const ComponentDatatype = Cesium.ComponentDatatype; 6 | 7 | function testComponentReader(componentType) { 8 | const typedArray = ComponentDatatype.createTypedArray( 9 | componentType, 10 | [0, 1, 2], 11 | ); 12 | const dataView = new DataView(typedArray.buffer); 13 | const componentTypeByteLength = 14 | ComponentDatatype.getSizeInBytes(componentType); 15 | const componentReader = getComponentReader(componentType); 16 | const byteOffset = componentTypeByteLength; 17 | const numberOfComponents = 2; 18 | const result = new Array(numberOfComponents); 19 | componentReader( 20 | dataView, 21 | byteOffset, 22 | numberOfComponents, 23 | componentTypeByteLength, 24 | result, 25 | ); 26 | expect(result).toEqual([1, 2]); 27 | } 28 | 29 | describe("getComponentReader", () => { 30 | it("reads values", () => { 31 | testComponentReader(ComponentDatatype.BYTE); 32 | testComponentReader(ComponentDatatype.UNSIGNED_BYTE); 33 | testComponentReader(ComponentDatatype.SHORT); 34 | testComponentReader(ComponentDatatype.UNSIGNED_SHORT); 35 | testComponentReader(ComponentDatatype.INT); 36 | testComponentReader(ComponentDatatype.UNSIGNED_INT); 37 | testComponentReader(ComponentDatatype.FLOAT); 38 | testComponentReader(ComponentDatatype.DOUBLE); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /specs/lib/getImageExtensionSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { RuntimeError } = require("cesium"); 4 | 5 | const dataUriToBuffer = require("../../lib/dataUriToBuffer"); 6 | const getImageExtension = require("../../lib/getImageExtension"); 7 | 8 | const pngData = dataUriToBuffer( 9 | "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gQcDxwOcoRpqQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAFElEQVQI12P8//8/AwwwMSAB3BwAlm4DBdoYksUAAAAASUVORK5CYII=", 10 | ); 11 | const gifData = dataUriToBuffer( 12 | "data:image/gif;base64,R0lGODdhBAAEAIAAAP///////ywAAAAABAAEAAACBISPCQUAOw==", 13 | ); 14 | const jpgData = dataUriToBuffer( 15 | "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wgARCAAEAAQDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAVSf/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABBQJ//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPwF//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgEBPwF//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQAGPwJ//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPyF//9oADAMBAAIAAwAAABCf/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPxB//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgEBPxB//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPxB//9k=", 16 | ); 17 | const bmpData = dataUriToBuffer( 18 | "data:image/bmp;base64,Qk1mAAAAAAAAADYAAAAoAAAABAAAAAQAAAABABgAAAAAADAAAAATCwAAEwsAAAAAAAAAAAAA////////////////////////////////////////////////////////////////", 19 | ); 20 | const ktx2Data = dataUriToBuffer( 21 | "data:image/ktx2:base64,q0tUWCAyMLsNChoKAAAAAAEAAABAAAAAQAAAAAAAAAAAAAAAAQAAAAEAAAABAAAAaAAAACwAAACUAAAAdAAAAAgBAAAAAAAA7QEAAAAAAAD1AgAAAAAAAPgAAAAAAAAAAAAAAAAAAAAsAAAAAAAAAAIAKACjAQIAAwMAAAAAAAAAAAAAAAA/AAAAAAAAAAAA/////xIAAABLVFhvcmllbnRhdGlvbgByZAAAAFUAAABLVFh3cml0ZXIAdG9rdHggdjQuMC5iZXRhMi41My5nZjgzZDI4NDUuZGlydHkgLyBsaWJrdHggdjQuMC5iZXRhMi41MS5nM2EzMWQ0ZGEuZGlydHkAAAAAHQBfAEcAAAAcAQAAYgAAAAAAAAAAAAAAAAAAAPgAAAAAAAAAAAAAACDAGwABAIBgclixPD5qPIA3EAEAAAJpL1qO+wKACQAAAAAAAAQCEAEAAABACMSHxYxC5qRfZDX/tDYozqTrsZwcLKFPOj8HAAjmamgUQQAA4G9md89jvYMNJJDEE0erNdUVV1pMAlpY6II/hRiwULCxywNMUggi6NVWCjb+FBYpBRcLawsfwLyBDyAEEpo8f13mE8N3+MzWN8aG0uInpReLBNfESNpLUhJjn6b0UYqK6fRJOijLPA7LtfXfpawtHOM5wslUOelTK5qmmCyvsvvBs9vv7W4xDJxWwcfD2u9chVzP234bZjCbzdBt9+rlcHEnF9Uqax51Mg7aopqvbDx89+Bm8yd0dfjyNdzXnWaTS7dwHQMKYiRu4hcUupGEncw9fArUMQahsqgFgqPLjReLeCFOmjpQB/VCqMhnZzzd2+xeHe83j+sRN/Cff4yZ8Q0Cxnpj/0d75e6dw3y0evn6/C0AwdsAARAGgie2geTzWDtGyh9KgpX8WJb+BoILSDZI+sB3AA8ABEAQhjVfWhkigV6/gQJxMCXBAADD9G9HsB/tCHQCO40d4H1eHXIIwiGiqaHGIEjX+AaACQAAAAAAEMRAAPxNflpzjZbtuGAsfCOdQtK6+1SN1+ekY6j7t75MEIfKzChq81nw7nJCXzrVkyZprvXxESlhruXkPYHimL3u9o3k57aC4TXqLc9ZhbxM8VrYjcZ4NtT2+55ZPICCeHUISHGmLmU+/LNbHx0jlpkCmnpHFAZSaHnCJ49gGUlMs7+K4PcBQ9KG618GBGDlxTULGQlp8NdIpU8tdzAFkCSPVU20irfukO/YSpScsCHeCglbH2ljLGR+7McSAoyLd9j0k06gjHZdYiPGpi0n6fX7PCd22juv/0qvP5YIoHnrGZEXMiFaw9chnA2C8POpNi0EQu/lwCZoILII", 22 | ); 23 | const basisData = dataUriToBuffer( 24 | "data:image/basis;base64,c0ITAE0AAA9wAAAAqewBAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAQBkAAAAKAAAAQCMAAAABQAAkQAAACsAAABNAAAAAAAAAAAAAAAAAAAAAAEAAQABAAEAvAAAAAEAAACPUwHABAAAAAAAAAICmAgAAAAAAEASBAATAAAAAAAAiABgAgAAAAAAABFUVVVVBQDBRAAAAAAAAPJfLQCYAAAAAAAAQAgAEwACAAAAAIgBwAQAAAAAAAACCAAA", 25 | ); 26 | const textData = dataUriToBuffer("data:text/plain;charset=utf-8,randomtext"); 27 | 28 | describe("getImageExtension", () => { 29 | it("gets image extension from buffer", () => { 30 | expect(getImageExtension(pngData)).toBe(".png"); 31 | expect(getImageExtension(gifData)).toBe(".gif"); 32 | expect(getImageExtension(jpgData)).toBe(".jpg"); 33 | expect(getImageExtension(bmpData)).toBe(".bmp"); 34 | expect(getImageExtension(ktx2Data)).toBe(".ktx2"); 35 | expect(getImageExtension(basisData)).toBe(".basis"); 36 | }); 37 | 38 | it("throws error if buffer does not contain image data", () => { 39 | let thrownError; 40 | try { 41 | getImageExtension(textData); 42 | } catch (e) { 43 | thrownError = e; 44 | } 45 | expect(thrownError).toEqual( 46 | new RuntimeError("Image data does not have valid header"), 47 | ); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /specs/lib/getJsonBufferPaddedSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const getJsonBufferPadded = require("../../lib/getJsonBufferPadded"); 3 | 4 | describe("getJsonBufferPadded", () => { 5 | it("get json buffer padded to 8 bytes", () => { 6 | const gltf = { 7 | asset: { 8 | version: "2.0", 9 | }, 10 | }; 11 | const string = JSON.stringify(gltf); 12 | expect(string.length).toBe(27); 13 | const bufferPadded = getJsonBufferPadded(gltf); 14 | expect(bufferPadded.length).toBe(32); 15 | expect(bufferPadded.readUInt8(27)).toBe(32); // Space 16 | expect(bufferPadded.readUInt8(28)).toBe(32); 17 | expect(bufferPadded.readUInt8(29)).toBe(32); 18 | expect(bufferPadded.readUInt8(30)).toBe(32); 19 | expect(bufferPadded.readUInt8(31)).toBe(32); 20 | }); 21 | 22 | it("get json buffer padded to 8 bytes relative to byte offset", () => { 23 | const gltf = { 24 | asset: { 25 | version: "2.0", 26 | }, 27 | }; 28 | const string = JSON.stringify(gltf); 29 | expect(string.length).toBe(27); 30 | const bufferPadded = getJsonBufferPadded(gltf, 20); 31 | expect(bufferPadded.length).toBe(28); 32 | expect(bufferPadded.readUInt8(27)).toBe(32); // Space 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /specs/lib/getStatisticsSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const getStatistics = require("../../lib/getStatistics"); 3 | 4 | describe("getStatistics", () => { 5 | const gltf = { 6 | accessors: [ 7 | { 8 | componentType: 5123, 9 | type: "SCALAR", 10 | count: 1, 11 | }, 12 | { 13 | componentType: 5123, 14 | type: "SCALAR", 15 | count: 2, 16 | }, 17 | { 18 | componentType: 5123, 19 | type: "SCALAR", 20 | count: 6, 21 | }, 22 | { 23 | componentType: 5126, 24 | type: "VEC3", 25 | count: 6, 26 | }, 27 | ], 28 | buffers: [ 29 | { 30 | byteLength: 140, 31 | }, 32 | { 33 | byteLength: 120, 34 | uri: "buffer.bin", 35 | }, 36 | ], 37 | images: [ 38 | { 39 | uri: "image.png", 40 | }, 41 | { 42 | uri: "data:image/png;", 43 | }, 44 | { 45 | uri: "image2.png", 46 | }, 47 | ], 48 | meshes: [ 49 | { 50 | primitives: [ 51 | { 52 | indices: 0, 53 | mode: 0, // POINTS 54 | }, 55 | { 56 | indices: 1, 57 | mode: 1, // LINES 58 | }, 59 | { 60 | attributes: { 61 | POSITION: 3, 62 | }, 63 | }, 64 | ], 65 | }, 66 | { 67 | primitives: [ 68 | { 69 | indices: 2, 70 | mode: 4, // TRIANGLES 71 | }, 72 | ], 73 | }, 74 | ], 75 | materials: [{}, {}], 76 | animations: [{}, {}, {}], 77 | nodes: [ 78 | { 79 | name: "rootNode", 80 | mesh: 0, 81 | }, 82 | ], 83 | }; 84 | 85 | it("returns statistics for a gltf", () => { 86 | const statistics = getStatistics(gltf); 87 | expect(statistics.buffersByteLength).toEqual(260); 88 | expect(statistics.numberOfImages).toEqual(3); 89 | expect(statistics.numberOfExternalRequests).toEqual(3); 90 | expect(statistics.numberOfDrawCalls).toEqual(4); 91 | expect(statistics.numberOfRenderedPrimitives).toEqual(6); 92 | expect(statistics.numberOfNodes).toEqual(1); 93 | expect(statistics.numberOfMeshes).toEqual(2); 94 | expect(statistics.numberOfMaterials).toEqual(2); 95 | expect(statistics.numberOfAnimations).toEqual(3); 96 | }); 97 | 98 | it("returns draw call statistics for a gltf node", () => { 99 | const statistics = getStatistics(gltf, 0); 100 | expect(statistics.numberOfDrawCalls).toEqual(3); 101 | expect(statistics.numberOfRenderedPrimitives).toEqual(4); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /specs/lib/glbToGltfSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const fsExtra = require("fs-extra"); 3 | const glbToGltf = require("../../lib/glbToGltf"); 4 | 5 | const glbPath = "specs/data/2.0/box-textured-binary/box-textured-binary.glb"; 6 | 7 | describe("glbToGltf", () => { 8 | it("glbToGltf", async () => { 9 | spyOn(console, "log"); 10 | const glb = fsExtra.readFileSync(glbPath); 11 | const options = { 12 | separate: true, 13 | stats: true, 14 | }; 15 | const results = await glbToGltf(glb, options); 16 | expect(results.gltf).toBeDefined(); 17 | expect(results.separateResources).toBeDefined(); 18 | expect(results.gltf.buffers.length).toBe(1); 19 | expect(console.log).toHaveBeenCalled(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /specs/lib/gltfToGlbSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const fsExtra = require("fs-extra"); 3 | const path = require("path"); 4 | 5 | const gltfToGlb = require("../../lib/gltfToGlb"); 6 | const parseGlb = require("../../lib/parseGlb"); 7 | 8 | const gltfPath = 9 | "specs/data/2.0/box-textured-embedded/box-textured-embedded.gltf"; 10 | const gltfMeshoptFallbackPath = 11 | "specs/data/2.0/extensions/EXT_meshopt_compression/meshopt-fallback/meshopt-fallback.gltf"; 12 | 13 | describe("gltfToGlb", () => { 14 | it("gltfToGlb", async () => { 15 | spyOn(console, "log"); 16 | const gltf = fsExtra.readJsonSync(gltfPath); 17 | const options = { 18 | separateTextures: true, 19 | stats: true, 20 | }; 21 | const results = await gltfToGlb(gltf, options); 22 | const glb = results.glb; 23 | const separateResources = results.separateResources; 24 | expect(Buffer.isBuffer(glb)).toBe(true); 25 | expect(Object.keys(separateResources).length).toBe(1); 26 | expect(console.log).toHaveBeenCalled(); 27 | 28 | // Header + JSON header + JSON content + binary header + binary content 29 | const glbLength = glb.readUInt32LE(8); 30 | const jsonChunkLength = glb.readUInt32LE(12); 31 | const binaryChunkLength = glb.readUInt32LE(12 + 8 + jsonChunkLength); 32 | const expectedLength = 12 + 8 + jsonChunkLength + 8 + binaryChunkLength; 33 | expect(glbLength).toBe(expectedLength); 34 | expect(glb.length).toBe(expectedLength); 35 | }); 36 | 37 | it("gltfToGlb with separate resources", async () => { 38 | spyOn(console, "log"); 39 | const gltf = fsExtra.readJsonSync(gltfPath); 40 | const options = { 41 | separate: true, 42 | stats: true, 43 | }; 44 | const results = await gltfToGlb(gltf, options); 45 | const glb = results.glb; 46 | const separateResources = results.separateResources; 47 | expect(Buffer.isBuffer(glb)).toBe(true); 48 | expect(Object.keys(separateResources).length).toBe(2); 49 | expect(console.log).toHaveBeenCalled(); 50 | 51 | // Header + JSON header + JSON content. No binary header or content. 52 | const glbLength = glb.readUInt32LE(8); 53 | const jsonChunkLength = glb.readUInt32LE(12); 54 | const expectedLength = 12 + 8 + jsonChunkLength; 55 | expect(glbLength).toBe(expectedLength); 56 | expect(glb.length).toBe(expectedLength); 57 | }); 58 | 59 | it("gltfToGlb processes glTF with EXT_meshopt_compression extension.", async () => { 60 | const gltf = fsExtra.readJsonSync(gltfMeshoptFallbackPath); 61 | const options = { 62 | resourceDirectory: path.dirname(gltfMeshoptFallbackPath), 63 | }; 64 | const results = await gltfToGlb(gltf, options); 65 | expect(results.glb).toBeDefined(); 66 | 67 | const processedGltf = parseGlb(results.glb); 68 | 69 | expect(processedGltf).toBeDefined(); 70 | expect(processedGltf.buffers.length).toBe(1); 71 | 72 | const buffer0 = processedGltf.buffers[0]; 73 | 74 | const bufferView0 = processedGltf.bufferViews[0]; 75 | const bufferView1 = processedGltf.bufferViews[1]; 76 | const bufferView2 = processedGltf.bufferViews[2]; 77 | 78 | const meshoptObject0 = bufferView0.extensions.EXT_meshopt_compression; 79 | const meshoptObject1 = bufferView1.extensions.EXT_meshopt_compression; 80 | const meshoptObject2 = bufferView2.extensions.EXT_meshopt_compression; 81 | 82 | expect(buffer0.byteLength).toBe(1432); 83 | expect(buffer0.uri).not.toBeDefined(); 84 | 85 | expect(bufferView0.buffer).toBe(0); 86 | expect(bufferView0.byteOffset).toBe(0); 87 | expect(bufferView0.byteLength).toBe(400); 88 | expect(meshoptObject0.buffer).toBe(0); 89 | expect(meshoptObject0.byteOffset).toBe(400); 90 | expect(meshoptObject0.byteLength).toBe(265); 91 | 92 | expect(bufferView1.buffer).toBe(0); 93 | expect(bufferView1.byteOffset).toBe(672); 94 | expect(bufferView1.byteLength).toBe(200); 95 | expect(meshoptObject1.buffer).toBe(0); 96 | expect(meshoptObject1.byteOffset).toBe(872); 97 | expect(meshoptObject1.byteLength).toBe(117); 98 | 99 | expect(bufferView2.buffer).toBe(0); 100 | expect(bufferView2.byteOffset).toBe(992); 101 | expect(bufferView2.byteLength).toBe(360); 102 | expect(meshoptObject2.buffer).toBe(0); 103 | expect(meshoptObject2.byteOffset).toBe(1352); 104 | expect(meshoptObject2.byteLength).toBe(78); 105 | }); 106 | 107 | it("gltfToGlb processes glTF with EXT_meshopt_compression extension with separate resources.", async () => { 108 | const gltf = fsExtra.readJsonSync(gltfMeshoptFallbackPath); 109 | const options = { 110 | separate: true, 111 | resourceDirectory: path.dirname(gltfMeshoptFallbackPath), 112 | }; 113 | const results = await gltfToGlb(gltf, options); 114 | expect(results.glb).toBeDefined(); 115 | 116 | const processedGltf = parseGlb(results.glb); 117 | 118 | expect(processedGltf).toBeDefined(); 119 | expect(processedGltf.buffers.length).toBe(2); 120 | 121 | const buffer0 = processedGltf.buffers[0]; 122 | const buffer1 = processedGltf.buffers[1]; 123 | 124 | const bufferView0 = gltf.bufferViews[0]; 125 | const bufferView1 = gltf.bufferViews[1]; 126 | const bufferView2 = gltf.bufferViews[2]; 127 | 128 | const meshoptObject0 = bufferView0.extensions.EXT_meshopt_compression; 129 | const meshoptObject1 = bufferView1.extensions.EXT_meshopt_compression; 130 | const meshoptObject2 = bufferView2.extensions.EXT_meshopt_compression; 131 | 132 | expect(buffer0.byteLength).toBe(960); 133 | expect(buffer0.uri).toBe("meshopt-fallback-meshopt-fallback-1.bin"); 134 | expect(buffer1.byteLength).toBe(472); 135 | expect(buffer1.uri).toBe("meshopt-fallback.bin"); 136 | 137 | expect(bufferView0.buffer).toBe(0); 138 | expect(bufferView0.byteOffset).toBe(0); 139 | expect(bufferView0.byteLength).toBe(400); 140 | expect(meshoptObject0.buffer).toBe(1); 141 | expect(meshoptObject0.byteOffset).toBe(0); 142 | expect(meshoptObject0.byteLength).toBe(265); 143 | 144 | expect(bufferView1.buffer).toBe(0); 145 | expect(bufferView1.byteOffset).toBe(400); 146 | expect(bufferView1.byteLength).toBe(200); 147 | expect(meshoptObject1.buffer).toBe(1); 148 | expect(meshoptObject1.byteOffset).toBe(272); 149 | expect(meshoptObject1.byteLength).toBe(117); 150 | 151 | expect(bufferView2.buffer).toBe(0); 152 | expect(bufferView2.byteOffset).toBe(600); 153 | expect(bufferView2.byteLength).toBe(360); 154 | expect(meshoptObject2.buffer).toBe(1); 155 | expect(meshoptObject2.byteOffset).toBe(392); 156 | expect(meshoptObject2.byteLength).toBe(78); 157 | }); 158 | }); 159 | -------------------------------------------------------------------------------- /specs/lib/moveTechniquesToExtensionSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const moveTechniquesToExtension = require("../../lib/moveTechniquesToExtension"); 4 | 5 | const WebGLConstants = Cesium.WebGLConstants; 6 | describe("moveTechniquesToExtension", () => { 7 | it("moves techniques, shaders, and programs to extension", () => { 8 | const gltf = { 9 | programs: { 10 | program_0: { 11 | attributes: ["a_normal", "a_position", "a_texcoord0"], 12 | fragmentShader: "BoxTextured0FS", 13 | vertexShader: "BoxTextured0VS", 14 | }, 15 | program_1: { 16 | attributes: ["a_normal", "a_position", "a_texcoord0"], 17 | fragmentShader: "BoxTextured1FS", 18 | vertexShader: "BoxTextured0VS", 19 | }, 20 | }, 21 | shaders: { 22 | BoxTextured0FS: { 23 | type: WebGLConstants.FRAGMENT_SHADER, 24 | uri: "BoxTextured0FS.glsl", 25 | }, 26 | BoxTextured0VS: { 27 | type: WebGLConstants.VERTEX_SHADER, 28 | uri: "BoxTextured0VS.glsl", 29 | }, 30 | BoxTextured1FS: { 31 | type: WebGLConstants.FRAGMENT_SHADER, 32 | uri: "BoxTextured1FS.glsl", 33 | }, 34 | }, 35 | techniques: { 36 | technique0: { 37 | attributes: { 38 | a_normal: "normal", 39 | a_position: "position", 40 | a_texcoord0: "texcoord0", 41 | }, 42 | parameters: { 43 | diffuse: { 44 | type: WebGLConstants.SAMPLER_2D, 45 | }, 46 | modelViewMatrix: { 47 | semantic: "MODELVIEW", 48 | type: WebGLConstants.FLOAT_MAT4, 49 | }, 50 | normal: { 51 | semantic: "NORMAL", 52 | type: WebGLConstants.FLOAT_VEC3, 53 | }, 54 | normalMatrix: { 55 | semantic: "MODELVIEWINVERSETRANSPOSE", 56 | type: WebGLConstants.FLOAT_MAT3, 57 | }, 58 | position: { 59 | semantic: "POSITION", 60 | type: WebGLConstants.FLOAT_VEC3, 61 | }, 62 | projectionMatrix: { 63 | semantic: "PROJECTION", 64 | type: WebGLConstants.FLOAT_MAT4, 65 | }, 66 | shininess: { 67 | type: WebGLConstants.FLOAT, 68 | }, 69 | specular: { 70 | type: WebGLConstants.FLOAT_VEC4, 71 | }, 72 | texcoord0: { 73 | semantic: "TEXCOORD_0", 74 | type: WebGLConstants.FLOAT_VEC2, 75 | }, 76 | }, 77 | program: "program_0", 78 | states: { 79 | enable: [WebGLConstants.DEPTH_TEST, WebGLConstants.CULL_FACE], 80 | }, 81 | uniforms: { 82 | u_diffuse: "diffuse", 83 | u_modelViewMatrix: "modelViewMatrix", 84 | u_normalMatrix: "normalMatrix", 85 | u_projectionMatrix: "projectionMatrix", 86 | u_shininess: "shininess", 87 | u_specular: "specular", 88 | }, 89 | }, 90 | technique1: { 91 | program: "program_1", 92 | }, 93 | technique2: { 94 | parameters: { 95 | diffuse: { 96 | type: WebGLConstants.FLOAT_VEC4, 97 | }, 98 | }, 99 | program: "program_0", 100 | uniforms: { 101 | u_diffuse: "diffuse", 102 | }, 103 | }, 104 | }, 105 | materials: [ 106 | { 107 | name: "Texture", 108 | technique: "technique0", 109 | values: { 110 | diffuse: "texture_Image0001", 111 | shininess: 256, 112 | specular: [0.2, 0.2, 0.2, 1], 113 | }, 114 | }, 115 | { 116 | name: "Color", 117 | technique: "technique2", 118 | values: { 119 | diffuse: [0.2, 0.2, 0.2, 1], 120 | }, 121 | }, 122 | ], 123 | }; 124 | 125 | const gltfWithTechniquesWebgl = moveTechniquesToExtension(gltf); 126 | expect(gltfWithTechniquesWebgl.extensions).toBeDefined(); 127 | const techniques = gltfWithTechniquesWebgl.extensions.KHR_techniques_webgl; 128 | expect(techniques).toBeDefined(); 129 | expect(techniques.techniques.length).toBe(3); 130 | 131 | const technique = techniques.techniques[0]; 132 | const attributes = technique.attributes; 133 | expect(attributes).toBeDefined(); 134 | expect(attributes.a_position.semantic).toBe("POSITION"); 135 | 136 | const uniforms = technique.uniforms; 137 | expect(uniforms).toBeDefined(); 138 | expect(uniforms.u_modelViewMatrix.semantic).toBe("MODELVIEW"); 139 | expect(uniforms.u_modelViewMatrix.type).toBe(WebGLConstants.FLOAT_MAT4); 140 | 141 | expect(technique.program).toBe(0); 142 | expect(technique.parameters).toBeUndefined(); 143 | expect(technique.states).toBeUndefined(); 144 | 145 | expect(techniques.programs.length).toBe(2); 146 | const program = techniques.programs[technique.program]; 147 | expect(program).toBeDefined(); 148 | 149 | expect(techniques.shaders.length).toBe(3); 150 | expect(techniques.shaders[program.fragmentShader].type).toBe( 151 | WebGLConstants.FRAGMENT_SHADER, 152 | ); 153 | expect(techniques.shaders[program.vertexShader].type).toBe( 154 | WebGLConstants.VERTEX_SHADER, 155 | ); 156 | 157 | expect(gltfWithTechniquesWebgl.techniques).toBeUndefined(); 158 | expect(gltfWithTechniquesWebgl.programs).toBeUndefined(); 159 | expect(gltfWithTechniquesWebgl.shaders).toBeUndefined(); 160 | 161 | const material = gltf.materials[0]; 162 | expect(material.extensions).toBeDefined(); 163 | const materialTechniques = material.extensions.KHR_techniques_webgl; 164 | expect(materialTechniques).toBeDefined(); 165 | expect(materialTechniques.technique).toBe(0); 166 | expect(materialTechniques.values.u_shininess).toBe(256); 167 | expect(materialTechniques.values.u_diffuse).toBe("texture_Image0001"); 168 | 169 | expect(material.technique).toBeUndefined(); 170 | expect(material.values).toBeUndefined(); 171 | 172 | const technique2 = techniques.techniques[1]; 173 | const program2 = techniques.programs[technique2.program]; 174 | expect(program2.vertexShader).toBe(program.vertexShader); 175 | expect(program2.fragmentShader).not.toBe(program.fragmentShader); 176 | 177 | const technique3 = techniques.techniques[2]; 178 | expect(technique3.program).toBe(0); 179 | expect(technique3.uniforms.u_diffuse.type).toBe(WebGLConstants.FLOAT_VEC4); 180 | expect( 181 | gltf.materials[1].extensions.KHR_techniques_webgl.values.u_diffuse, 182 | ).toEqual([0.2, 0.2, 0.2, 1.0]); 183 | }); 184 | }); 185 | -------------------------------------------------------------------------------- /specs/lib/numberOfComponentsForTypeSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const numberOfComponentsForType = require("../../lib/numberOfComponentsForType"); 3 | 4 | describe("numberOfComponentsForType", () => { 5 | it("numberOfComponentsForType", () => { 6 | expect(numberOfComponentsForType("SCALAR")).toBe(1); 7 | expect(numberOfComponentsForType("VEC2")).toBe(2); 8 | expect(numberOfComponentsForType("VEC3")).toBe(3); 9 | expect(numberOfComponentsForType("VEC4")).toBe(4); 10 | expect(numberOfComponentsForType("MAT2")).toBe(4); 11 | expect(numberOfComponentsForType("MAT3")).toBe(9); 12 | expect(numberOfComponentsForType("MAT4")).toBe(16); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /specs/lib/parseGlbSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { RuntimeError } = require("cesium"); 4 | 5 | const parseGlb = require("../../lib/parseGlb"); 6 | const removePipelineExtras = require("../../lib/removePipelineExtras"); 7 | 8 | describe("parseGlb", () => { 9 | it("throws an error with invalid magic", () => { 10 | const glb = Buffer.alloc(20); 11 | glb.write("NOPE", 0); 12 | 13 | let thrownError; 14 | try { 15 | parseGlb(glb); 16 | } catch (e) { 17 | thrownError = e; 18 | } 19 | expect(thrownError).toEqual( 20 | new RuntimeError("File is not valid binary glTF"), 21 | ); 22 | }); 23 | 24 | it("throws an error if version is not 1 or 2", () => { 25 | const glb = Buffer.alloc(20); 26 | glb.write("glTF", 0); 27 | glb.writeUInt32LE(3, 4); 28 | 29 | let thrownError; 30 | try { 31 | parseGlb(glb); 32 | } catch (e) { 33 | thrownError = e; 34 | } 35 | expect(thrownError).toEqual( 36 | new RuntimeError("Binary glTF version is not 1 or 2"), 37 | ); 38 | }); 39 | 40 | describe("1.0", () => { 41 | it("throws an error if content format is not JSON", () => { 42 | const glb = Buffer.alloc(20); 43 | glb.write("glTF", 0); 44 | glb.writeUInt32LE(1, 4); 45 | glb.writeUInt32LE(20, 8); 46 | glb.writeUInt32LE(0, 12); 47 | glb.writeUInt32LE(1, 16); 48 | 49 | let thrownError; 50 | try { 51 | parseGlb(glb); 52 | } catch (e) { 53 | thrownError = e; 54 | } 55 | expect(thrownError).toEqual( 56 | new RuntimeError("Binary glTF scene format is not JSON"), 57 | ); 58 | }); 59 | 60 | it("loads binary glTF", () => { 61 | const binaryData = Buffer.from([0, 1, 2, 3, 4, 5]); 62 | const gltf = { 63 | bufferViews: { 64 | imageBufferView: { 65 | byteLength: 0, 66 | }, 67 | shaderBufferView: { 68 | byteLength: 0, 69 | }, 70 | }, 71 | buffers: { 72 | binary_glTF: { 73 | byteLength: binaryData.length, 74 | uri: "data:,", 75 | }, 76 | }, 77 | images: { 78 | image: { 79 | extensions: { 80 | KHR_binary_glTF: { 81 | bufferView: "imageBufferView", 82 | mimeType: "image/jpg", 83 | }, 84 | }, 85 | }, 86 | }, 87 | shaders: { 88 | shader: { 89 | extensions: { 90 | KHR_binary_glTF: { 91 | bufferView: "shaderBufferView", 92 | }, 93 | }, 94 | }, 95 | }, 96 | extensionsUsed: ["KHR_binary_glTF"], 97 | }; 98 | let gltfString = JSON.stringify(gltf); 99 | while (gltfString.length % 4 !== 0) { 100 | gltfString += " "; 101 | } 102 | const glb = Buffer.alloc(20 + gltfString.length + binaryData.length); 103 | glb.write("glTF", 0); 104 | glb.writeUInt32LE(1, 4); 105 | glb.writeUInt32LE(20 + gltfString.length + binaryData.length, 8); 106 | glb.writeUInt32LE(gltfString.length, 12); 107 | glb.writeUInt32LE(0, 16); 108 | glb.write(gltfString, 20); 109 | binaryData.copy(glb, 20 + gltfString.length); 110 | 111 | const parsedGltf = parseGlb(glb); 112 | expect(parsedGltf.extensionsUsed).toBeUndefined(); 113 | const buffer = parsedGltf.buffers.binary_glTF; 114 | for (let i = 0; i < binaryData.length; i++) { 115 | expect(buffer.extras._pipeline.source[i]).toEqual(binaryData[i]); 116 | expect(buffer.uri).toBeUndefined(); 117 | } 118 | 119 | const image = parsedGltf.images.image; 120 | expect(image.extensions.KHR_binary_glTF).toBeDefined(); 121 | expect(image.extensions.KHR_binary_glTF.bufferView).toBe( 122 | "imageBufferView", 123 | ); 124 | expect(image.extensions.KHR_binary_glTF.mimeType).toBe("image/jpg"); 125 | const shader = parsedGltf.shaders.shader; 126 | expect(shader.extensions.KHR_binary_glTF).toBeDefined(); 127 | expect(shader.extensions.KHR_binary_glTF.bufferView).toBe( 128 | "shaderBufferView", 129 | ); 130 | }); 131 | }); 132 | 133 | describe("2.0", () => { 134 | it("loads binary glTF", () => { 135 | let i; 136 | const binaryData = Buffer.from([0, 1, 2, 3, 4, 5]); 137 | const gltf = { 138 | asset: { 139 | version: "2.0", 140 | }, 141 | buffers: [ 142 | { 143 | byteLength: binaryData.length, 144 | }, 145 | ], 146 | images: [ 147 | { 148 | bufferView: 0, 149 | mimeType: "image/jpg", 150 | }, 151 | ], 152 | }; 153 | let gltfString = JSON.stringify(gltf); 154 | while (gltfString.length % 4 !== 0) { 155 | gltfString += " "; 156 | } 157 | const glb = Buffer.alloc(28 + gltfString.length + binaryData.length); 158 | glb.write("glTF", 0); 159 | glb.writeUInt32LE(2, 4); 160 | glb.writeUInt32LE(12 + 8 + gltfString.length + 8 + binaryData.length, 8); 161 | glb.writeUInt32LE(gltfString.length, 12); 162 | glb.writeUInt32LE(0x4e4f534a, 16); 163 | glb.write(gltfString, 20); 164 | glb.writeUInt32LE(binaryData.length, 20 + gltfString.length); 165 | glb.writeUInt32LE(0x004e4942, 24 + gltfString.length); 166 | binaryData.copy(glb, 28 + gltfString.length); 167 | 168 | const parsedGltf = parseGlb(glb); 169 | const buffer = parsedGltf.buffers[0]; 170 | for (i = 0; i < binaryData.length; i++) { 171 | expect(buffer.extras._pipeline.source[i]).toEqual(binaryData[i]); 172 | } 173 | removePipelineExtras(parsedGltf); 174 | expect(parsedGltf).toEqual(gltf); 175 | }); 176 | }); 177 | }); 178 | -------------------------------------------------------------------------------- /specs/lib/processGlbSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const fsExtra = require("fs-extra"); 3 | const processGlb = require("../../lib/processGlb"); 4 | 5 | const glbPath = "specs/data/2.0/box-textured-binary/box-textured-binary.glb"; 6 | 7 | describe("processGlb", () => { 8 | it("processGlb", async () => { 9 | spyOn(console, "log"); 10 | const glb = fsExtra.readFileSync(glbPath); 11 | const options = { 12 | separate: true, 13 | stats: true, 14 | }; 15 | const results = await processGlb(glb, options); 16 | expect(Buffer.isBuffer(results.glb)).toBe(true); 17 | expect(results.separateResources).toBeDefined(); 18 | expect(console.log).toHaveBeenCalled(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /specs/lib/readAccessorPackedSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const readAccessorPacked = require("../../lib/readAccessorPacked"); 3 | const readResources = require("../../lib/readResources"); 4 | 5 | const contiguousData = [ 6 | -1.0, 1.0, -1.0, 0.0, 0.0, 0.0, 3.0, 2.0, 1.0, -1.0, -2.0, -3.0, 7 | ]; 8 | 9 | const nan = Number.NaN; 10 | const nonContiguousData = [ 11 | -1.0, 12 | 1.0, 13 | -1.0, 14 | nan, 15 | nan, 16 | nan, 17 | 0.0, 18 | 0.0, 19 | 0.0, 20 | nan, 21 | nan, 22 | nan, 23 | 3.0, 24 | 2.0, 25 | 1.0, 26 | nan, 27 | nan, 28 | nan, 29 | -1.0, 30 | -2.0, 31 | -3.0, 32 | nan, 33 | nan, 34 | nan, 35 | ]; 36 | 37 | function createGltf(elements, byteStride) { 38 | const buffer = Buffer.from(new Float32Array(elements).buffer); 39 | const byteLength = buffer.length; 40 | const dataUri = `data:application/octet-stream;base64,${buffer.toString( 41 | "base64", 42 | )}`; 43 | const gltf = { 44 | accessors: [ 45 | { 46 | bufferView: 0, 47 | byteOffset: 0, 48 | componentType: 5126, 49 | count: 4, 50 | type: "VEC3", 51 | }, 52 | ], 53 | bufferViews: [ 54 | { 55 | buffer: 0, 56 | byteOffset: 0, 57 | byteLength: byteLength, 58 | byteStride: byteStride, 59 | }, 60 | ], 61 | buffers: [ 62 | { 63 | uri: dataUri, 64 | byteLength: byteLength, 65 | }, 66 | ], 67 | }; 68 | return readResources(gltf); 69 | } 70 | 71 | describe("readAccessorPacked", () => { 72 | it("reads contiguous accessor", async () => { 73 | const gltf = await createGltf(contiguousData, 12); 74 | expect(readAccessorPacked(gltf, gltf.accessors[0])).toEqual(contiguousData); 75 | }); 76 | 77 | it("reads non-contiguous accessor", async () => { 78 | const gltf = await createGltf(nonContiguousData, 24); 79 | expect(readAccessorPacked(gltf, gltf.accessors[0])).toEqual(contiguousData); 80 | }); 81 | 82 | it("reads accessor that does not have a buffer view", () => { 83 | const gltf = { 84 | accessors: [ 85 | { 86 | componentType: 5126, 87 | count: 4, 88 | type: "VEC3", 89 | }, 90 | ], 91 | }; 92 | const expected = new Array(12).fill(0); 93 | expect(readAccessorPacked(gltf, gltf.accessors[0])).toEqual(expected); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /specs/lib/readResourcesSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const fsExtra = require("fs-extra"); 4 | const path = require("path"); 5 | const ForEach = require("../../lib/ForEach"); 6 | const parseGlb = require("../../lib/parseGlb"); 7 | const readResources = require("../../lib/readResources"); 8 | 9 | const RuntimeError = Cesium.RuntimeError; 10 | 11 | const boxTexturedSeparate1Path = 12 | "specs/data/1.0/box-textured-separate/box-textured-separate.gltf"; 13 | const boxTexturedBinarySeparate1Path = 14 | "specs/data/1.0/box-textured-binary-separate/box-textured-binary-separate.glb"; 15 | const boxTexturedBinary1Path = 16 | "specs/data/1.0/box-textured-binary/box-textured-binary.glb"; 17 | const boxTexturedEmbedded1Path = 18 | "specs/data/1.0/box-textured-embedded/box-textured-embedded.gltf"; 19 | const boxTexturedSeparate2Path = 20 | "specs/data/2.0/box-textured-separate/box-textured-separate.gltf"; 21 | const boxTexturedBinarySeparate2Path = 22 | "specs/data/2.0/box-textured-binary-separate/box-textured-binary-separate.glb"; 23 | const boxTexturedBinary2Path = 24 | "specs/data/2.0/box-textured-binary/box-textured-binary.glb"; 25 | const boxTexturedEmbedded2Path = 26 | "specs/data/2.0/box-textured-embedded/box-textured-embedded.gltf"; 27 | 28 | function readGltf(gltfPath, binary) { 29 | if (binary) { 30 | const glb = fsExtra.readFileSync(gltfPath); 31 | return parseGlb(glb); 32 | } 33 | return fsExtra.readJsonSync(gltfPath); 34 | } 35 | 36 | function checkPaths(object, resourceDirectory) { 37 | const pipelineExtras = object.extras._pipeline; 38 | const absolutePath = pipelineExtras.absolutePath; 39 | const relativePath = pipelineExtras.relativePath; 40 | expect(path.basename(relativePath)).toBe(relativePath); 41 | expect(absolutePath).toBe(path.join(resourceDirectory, relativePath)); 42 | expect(object.name).toBe( 43 | path.basename(relativePath, path.extname(relativePath)), 44 | ); 45 | } 46 | 47 | async function readsResources(gltfPath, binary, separate) { 48 | const gltf = readGltf(gltfPath, binary); 49 | const resourceDirectory = path.resolve(path.dirname(gltfPath)); 50 | const options = { 51 | resourceDirectory: resourceDirectory, 52 | }; 53 | await readResources(gltf, options); 54 | ForEach.shader(gltf, (shader) => { 55 | const shaderText = shader.extras._pipeline.source; 56 | expect(typeof shaderText === "string").toBe(true); 57 | expect(shaderText.length).toBeGreaterThan(0); 58 | expect(shader.uri).toBeUndefined(); 59 | if (separate) { 60 | checkPaths(shader, resourceDirectory); 61 | } 62 | }); 63 | ForEach.image(gltf, (image) => { 64 | const imageSource = image.extras._pipeline.source; 65 | expect(Buffer.isBuffer(imageSource)).toBe(true); 66 | expect(image.uri).toBeUndefined(); 67 | if (separate) { 68 | checkPaths(image, resourceDirectory); 69 | } 70 | }); 71 | ForEach.buffer(gltf, (buffer) => { 72 | const bufferSource = buffer.extras._pipeline.source; 73 | expect(Buffer.isBuffer(bufferSource)).toBe(true); 74 | expect(buffer.uri).toBeUndefined(); 75 | if (separate && !binary) { 76 | checkPaths(buffer, resourceDirectory); 77 | } 78 | }); 79 | } 80 | 81 | describe("readResources", () => { 82 | it("reads separate resources from 1.0 model", async () => { 83 | await readsResources(boxTexturedSeparate1Path, false, true); 84 | }); 85 | 86 | it("reads separate resources from 1.0 glb", async () => { 87 | await readsResources(boxTexturedBinarySeparate1Path, true, true); 88 | }); 89 | 90 | it("reads embedded resources from 1.0 model", async () => { 91 | await readsResources(boxTexturedEmbedded1Path, false, false); 92 | }); 93 | 94 | it("reads resources from 1.0 glb", async () => { 95 | await readsResources(boxTexturedBinary1Path, true, false); 96 | }); 97 | 98 | it("reads separate resources from model", async () => { 99 | await readsResources(boxTexturedSeparate2Path, false, true); 100 | }); 101 | 102 | it("reads separate resources from glb", async () => { 103 | await readsResources(boxTexturedBinarySeparate2Path, true, true); 104 | }); 105 | 106 | it("reads embedded resources from model", async () => { 107 | await readsResources(boxTexturedEmbedded2Path, false, false); 108 | }); 109 | 110 | it("reads resources from glb", async () => { 111 | await readsResources(boxTexturedBinary2Path, true, false); 112 | }); 113 | 114 | it("rejects if gltf contains separate resources but no resource directory is supplied", async () => { 115 | const gltf = readGltf(boxTexturedSeparate2Path); 116 | 117 | let thrownError; 118 | try { 119 | await readResources(gltf); 120 | } catch (e) { 121 | thrownError = e; 122 | } 123 | expect(thrownError).toEqual( 124 | new RuntimeError( 125 | "glTF model references separate files but no resourceDirectory is supplied", 126 | ), 127 | ); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /specs/lib/removeDefaultsSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const removeDefaults = require("../../lib/removeDefaults"); 3 | 4 | describe("removeDefaults", () => { 5 | it("removeDefaults", () => { 6 | const gltf = { 7 | nodes: [ 8 | { 9 | matrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 10 | }, 11 | { 12 | matrix: [2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 13 | }, 14 | ], 15 | accessors: [ 16 | { 17 | bufferView: 0, 18 | normalized: false, 19 | }, 20 | ], 21 | }; 22 | removeDefaults(gltf); 23 | expect(gltf.nodes[0].matrix).toBeUndefined(); 24 | expect(gltf.nodes[1].matrix).toBeDefined(); 25 | expect(gltf.accessors[0].normalized).toBeUndefined(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /specs/lib/removeExtensionSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const removeExtension = require("../../lib/removeExtension"); 4 | 5 | const WebGLConstants = Cesium.WebGLConstants; 6 | 7 | describe("removeExtension", () => { 8 | it("removes extension", () => { 9 | const gltf = { 10 | extensionsRequired: ["extension1", "extension2", "extension3"], 11 | extensionsUsed: ["extension1", "extension2", "extension3"], 12 | extensions: { 13 | extension1: { 14 | value: 9, 15 | }, 16 | extension2: [0, 1, 2], 17 | }, 18 | materials: [ 19 | { 20 | baseColorFactor: [1.0, 0.0, 0.0, 1.0], 21 | extensions: { 22 | extension1: { 23 | value: 10, 24 | }, 25 | }, 26 | }, 27 | { 28 | baseColorFactor: [0.0, 0.0, 1.0, 1.0], 29 | extensions: { 30 | extension1: { 31 | value: 11, 32 | }, 33 | }, 34 | }, 35 | ], 36 | cameras: [ 37 | { 38 | extensions: { 39 | extension1: { 40 | value: 9, 41 | }, 42 | extension2: [3, 4, 5], 43 | }, 44 | }, 45 | ], 46 | }; 47 | const extension1 = removeExtension(gltf, "extension1"); 48 | expect(gltf.extensionsRequired).toEqual(["extension2", "extension3"]); 49 | expect(gltf.extensionsUsed).toEqual(["extension2", "extension3"]); 50 | expect(gltf.extensions).toEqual({ 51 | extension2: [0, 1, 2], 52 | }); 53 | expect(gltf.materials[0].extensions).toBeUndefined(); 54 | expect(gltf.materials[1].extensions).toBeUndefined(); 55 | expect(gltf.cameras[0].extensions).toEqual({ 56 | extension2: [3, 4, 5], 57 | }); 58 | expect(extension1).toEqual({ 59 | value: 9, 60 | }); 61 | 62 | const extension2 = removeExtension(gltf, "extension2"); 63 | expect(gltf.extensionsRequired).toEqual(["extension3"]); 64 | expect(gltf.extensionsUsed).toEqual(["extension3"]); 65 | expect(gltf.extensions).toBeUndefined(); 66 | expect(gltf.materials[0].extensions).toBeUndefined(); 67 | expect(gltf.materials[1].extensions).toBeUndefined(); 68 | expect(gltf.cameras[0].extensions).toBeUndefined(); 69 | expect(extension2).toEqual([0, 1, 2]); 70 | 71 | const extension3 = removeExtension(gltf, "extension3"); 72 | expect(gltf.extensionsRequired).toBeUndefined(); 73 | expect(gltf.extensionsUsed).toBeUndefined(); 74 | expect(gltf.extensions).toBeUndefined(); 75 | expect(gltf.materials[0].extensions).toBeUndefined(); 76 | expect(gltf.materials[1].extensions).toBeUndefined(); 77 | expect(gltf.cameras[0].extensions).toBeUndefined(); 78 | expect(extension3).toBeUndefined(); 79 | 80 | const emptyGltf = {}; 81 | removeExtension(gltf, "extension1"); 82 | expect(emptyGltf).toEqual({}); 83 | }); 84 | 85 | it("removes CESIUM_RTC extension", () => { 86 | const gltf = { 87 | extensionsRequired: ["CESIUM_RTC", "KHR_techniques_webgl"], 88 | extensionsUsed: ["CESIUM_RTC", "KHR_techniques_webgl"], 89 | extensions: { 90 | CESIUM_RTC: { 91 | center: [1.0, 2.0, 3.0], 92 | }, 93 | KHR_techniques_webgl: { 94 | techniques: [ 95 | { 96 | uniforms: { 97 | u_modelViewMatrix: { 98 | type: WebGLConstants.FLOAT_MAT4, 99 | semantic: "CESIUM_RTC_MODELVIEW", 100 | }, 101 | }, 102 | }, 103 | ], 104 | }, 105 | }, 106 | }; 107 | const extension = removeExtension(gltf, "CESIUM_RTC"); 108 | expect(extension).toEqual({ 109 | center: [1.0, 2.0, 3.0], 110 | }); 111 | expect(gltf.extensionsRequired).toEqual(["KHR_techniques_webgl"]); 112 | expect(gltf.extensionsUsed).toEqual(["KHR_techniques_webgl"]); 113 | expect( 114 | gltf.extensions.KHR_techniques_webgl.techniques[0].uniforms 115 | .u_modelViewMatrix.semantic, 116 | ).toBe("MODELVIEW"); 117 | }); 118 | }); 119 | -------------------------------------------------------------------------------- /specs/lib/removeExtensionsRequiredSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const removeExtensionsRequired = require("../../lib/removeExtensionsRequired"); 3 | 4 | describe("removeExtensionsRequired", () => { 5 | it("removes extension from extensionsRequired", () => { 6 | const gltf = { 7 | extensionsRequired: ["extension1", "extension2"], 8 | extensionsUsed: ["extension1", "extension2"], 9 | }; 10 | removeExtensionsRequired(gltf, "extension1"); 11 | expect(gltf.extensionsRequired).toEqual(["extension2"]); 12 | removeExtensionsRequired(gltf, "extension2"); 13 | expect(gltf.extensionsRequired).toBeUndefined(); 14 | expect(gltf.extensionsUsed).toEqual(["extension1", "extension2"]); 15 | 16 | const emptyGltf = {}; 17 | removeExtensionsRequired(gltf, "extension1"); 18 | expect(emptyGltf).toEqual({}); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /specs/lib/removeExtensionsUsedSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const removeExtensionsUsed = require("../../lib/removeExtensionsUsed"); 3 | 4 | describe("removeExtensionsUsed", () => { 5 | it("removes extension from extensionsUsed", () => { 6 | const gltf = { 7 | extensionsRequired: ["extension1", "extension2"], 8 | extensionsUsed: ["extension1", "extension2"], 9 | }; 10 | removeExtensionsUsed(gltf, "extension1"); 11 | expect(gltf.extensionsRequired).toEqual(["extension2"]); 12 | expect(gltf.extensionsUsed).toEqual(["extension2"]); 13 | 14 | removeExtensionsUsed(gltf, "extension2"); 15 | expect(gltf.extensionsRequired).toBeUndefined(); 16 | expect(gltf.extensionsUsed).toBeUndefined(); 17 | 18 | const emptyGltf = {}; 19 | removeExtensionsUsed(gltf, "extension1"); 20 | expect(emptyGltf).toEqual({}); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /specs/lib/removePipelineExtrasSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const addPipelineExtras = require("../../lib/addPipelineExtras"); 4 | const removePipelineExtras = require("../../lib/removePipelineExtras"); 5 | 6 | const WebGLConstants = Cesium.WebGLConstants; 7 | 8 | describe("removePipelineExtras", () => { 9 | it("removes pipeline extras", () => { 10 | const gltf = { 11 | buffers: [ 12 | { 13 | byteLength: 100, 14 | }, 15 | ], 16 | extensions: { 17 | KHR_techniques_webgl: { 18 | shaders: [ 19 | { 20 | type: WebGLConstants.VERTEX_SHADER, 21 | uri: "data:,", 22 | }, 23 | ], 24 | }, 25 | }, 26 | }; 27 | const gltfWithExtrasRemoved = removePipelineExtras(addPipelineExtras(gltf)); 28 | expect(gltfWithExtrasRemoved.buffers[0].extras).toBeUndefined(); 29 | expect( 30 | gltfWithExtrasRemoved.extensions.KHR_techniques_webgl.shaders[0].extras, 31 | ).toBeUndefined(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /specs/lib/updateAccessorComponentTypeSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Cesium = require("cesium"); 3 | const readResources = require("../../lib/readResources"); 4 | const updateAccessorComponentTypes = require("../../lib/updateAccessorComponentTypes"); 5 | 6 | const WebGLConstants = Cesium.WebGLConstants; 7 | 8 | let buffer; 9 | 10 | describe("updateAccessorComponentTypes", () => { 11 | beforeAll(() => { 12 | // Note: TypedArray constructors initialize all elements to zero 13 | const byteBuffer = Buffer.from(new Int8Array(96).buffer); 14 | const floatBuffer = Buffer.from(new Float32Array(96).buffer); 15 | const unsignedShortBuffer = Buffer.from(new Uint16Array(96).buffer); 16 | const source = Buffer.concat([ 17 | byteBuffer, 18 | floatBuffer, 19 | unsignedShortBuffer, 20 | ]); 21 | const byteLength = source.length; 22 | const dataUri = `data:application/octet-stream;base64,${source.toString( 23 | "base64", 24 | )}`; 25 | buffer = { 26 | uri: dataUri, 27 | byteLength: byteLength, 28 | }; 29 | }); 30 | 31 | it("converts joints accessor types", async () => { 32 | const gltf = { 33 | meshes: [ 34 | { 35 | primitives: [ 36 | { 37 | attributes: { 38 | JOINTS_0: 0, 39 | }, 40 | }, 41 | { 42 | attributes: { 43 | JOINTS_0: 1, 44 | }, 45 | }, 46 | { 47 | attributes: { 48 | JOINTS_0: 2, 49 | }, 50 | }, 51 | ], 52 | }, 53 | ], 54 | accessors: [ 55 | { 56 | bufferView: 0, 57 | componentType: WebGLConstants.BYTE, 58 | count: 24, 59 | type: "VEC4", 60 | }, 61 | { 62 | bufferView: 1, 63 | componentType: WebGLConstants.FLOAT, 64 | byteOffset: 12, 65 | count: 24, 66 | type: "VEC4", 67 | }, 68 | { 69 | bufferView: 2, 70 | componentType: WebGLConstants.UNSIGNED_SHORT, 71 | count: 24, 72 | type: "VEC4", 73 | }, 74 | ], 75 | bufferViews: [ 76 | { 77 | buffer: 0, 78 | byteOffset: 0, 79 | byteLength: 96, 80 | }, 81 | { 82 | buffer: 0, 83 | byteOffset: 96, 84 | byteLength: 384, 85 | }, 86 | { 87 | buffer: 0, 88 | byteOffset: 480, 89 | byteLength: 192, 90 | }, 91 | ], 92 | buffers: [buffer], 93 | }; 94 | 95 | await readResources(gltf); 96 | updateAccessorComponentTypes(gltf); 97 | 98 | expect(gltf.accessors.length).toBe(3); 99 | expect(gltf.bufferViews.length).toBe(5); 100 | expect(gltf.buffers.length).toBe(3); 101 | 102 | expect(gltf.accessors[0].componentType).toBe(WebGLConstants.UNSIGNED_BYTE); 103 | expect(gltf.accessors[0].bufferView).toBe(3); 104 | expect(gltf.bufferViews[3].buffer).toBe(1); 105 | expect(gltf.bufferViews[3].byteLength).toBe(96); 106 | 107 | expect(gltf.accessors[1].componentType).toBe(WebGLConstants.UNSIGNED_SHORT); 108 | expect(gltf.accessors[1].bufferView).toBe(4); 109 | expect(gltf.accessors[1].byteOffset).toBe(0); 110 | expect(gltf.bufferViews[4].buffer).toBe(2); 111 | expect(gltf.bufferViews[4].byteLength).toBe(192); 112 | 113 | expect(gltf.accessors[2].componentType).toBe(WebGLConstants.UNSIGNED_SHORT); 114 | expect(gltf.accessors[2].bufferView).toBe(2); 115 | }); 116 | 117 | it("converts weights accessor types", async () => { 118 | const gltf = { 119 | meshes: [ 120 | { 121 | primitives: [ 122 | { 123 | attributes: { 124 | WEIGHTS_0: 0, 125 | }, 126 | }, 127 | { 128 | attributes: { 129 | WEIGHTS_0: 1, 130 | }, 131 | }, 132 | { 133 | attributes: { 134 | WEIGHTS_0: 2, 135 | }, 136 | }, 137 | ], 138 | }, 139 | ], 140 | accessors: [ 141 | { 142 | bufferView: 0, 143 | componentType: WebGLConstants.FLOAT, 144 | count: 24, 145 | type: "VEC4", 146 | }, 147 | { 148 | bufferView: 1, 149 | componentType: WebGLConstants.BYTE, 150 | byteOffset: 12, 151 | count: 24, 152 | type: "VEC4", 153 | }, 154 | { 155 | bufferView: 2, 156 | componentType: WebGLConstants.SHORT, 157 | count: 24, 158 | type: "VEC4", 159 | }, 160 | ], 161 | bufferViews: [ 162 | { 163 | buffer: 0, 164 | byteLength: 12, 165 | }, 166 | { 167 | buffer: 0, 168 | byteOffset: 12, 169 | byteLength: 12, 170 | }, 171 | { 172 | buffer: 0, 173 | byteLength: 12, 174 | }, 175 | ], 176 | buffers: [buffer], 177 | }; 178 | 179 | await readResources(gltf); 180 | updateAccessorComponentTypes(gltf); 181 | 182 | expect(gltf.accessors.length).toBe(3); 183 | expect(gltf.bufferViews.length).toBe(5); 184 | expect(gltf.buffers.length).toBe(3); 185 | 186 | expect(gltf.accessors[0].componentType).toBe(WebGLConstants.FLOAT); 187 | expect(gltf.accessors[0].bufferView).toBe(0); 188 | 189 | expect(gltf.accessors[1].componentType).toBe(WebGLConstants.UNSIGNED_BYTE); 190 | expect(gltf.accessors[1].bufferView).toBe(3); 191 | expect(gltf.accessors[1].byteOffset).toBe(0); 192 | expect(gltf.bufferViews[3].buffer).toBe(1); 193 | expect(gltf.bufferViews[3].byteLength).toBe(96); 194 | 195 | expect(gltf.accessors[2].componentType).toBe(WebGLConstants.UNSIGNED_SHORT); 196 | expect(gltf.accessors[2].bufferView).toBe(4); 197 | expect(gltf.bufferViews[4].buffer).toBe(2); 198 | expect(gltf.bufferViews[4].byteLength).toBe(192); 199 | }); 200 | }); 201 | -------------------------------------------------------------------------------- /specs/lib/usesExtensionSpec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const usesExtension = require("../../lib/usesExtension"); 3 | 4 | describe("usesExtension", () => { 5 | it("uses extension", () => { 6 | const gltf = { 7 | extensionsUsed: ["extension1", "extension2"], 8 | }; 9 | expect(usesExtension(gltf, "extension1")).toBe(true); 10 | expect(usesExtension(gltf, "extension2")).toBe(true); 11 | expect(usesExtension(gltf, "extension3")).toBe(false); 12 | 13 | const emptyGltf = {}; 14 | expect(usesExtension(emptyGltf, "extension1")).toBe(false); 15 | }); 16 | }); 17 | --------------------------------------------------------------------------------