diff --git a/CHANGES.md b/CHANGES.md index ccde1f1..ae9afa0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ Change Log * Added back `inputUpAxis` and `outputUpAxis`. [#211](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/211) * Fixed specular image not being decoded when referenced by other textures. [#217](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/217) +* Fixed parsing faces that reference non-existing attributes. [#218](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/218) ### 3.0.4 - 2019-07-22 diff --git a/lib/loadObj.js b/lib/loadObj.js index 1052017..e893ab5 100644 --- a/lib/loadObj.js +++ b/lib/loadObj.js @@ -195,7 +195,10 @@ function loadObj(objPath, options) { function createVertex(p, u, n) { // Positions - if (defined(p)) { + if (defined(p) && (globalPositions.length > 0)) { + if (p * 3 >= globalPositions.length) { + throw new RuntimeError(`Position index ${p} is out of bounds`); + } const px = globalPositions.get(p * 3); const py = globalPositions.get(p * 3 + 1); const pz = globalPositions.get(p * 3 + 2); @@ -205,7 +208,10 @@ function loadObj(objPath, options) { } // Normals - if (defined(n)) { + if (defined(n) && (globalNormals.length > 0)) { + if (n * 3 >= globalNormals.length) { + throw new RuntimeError(`Normal index ${n} is out of bounds`); + } const nx = globalNormals.get(n * 3); const ny = globalNormals.get(n * 3 + 1); const nz = globalNormals.get(n * 3 + 2); @@ -215,7 +221,10 @@ function loadObj(objPath, options) { } // UVs - if (defined(u)) { + if (defined(u) && (globalUvs.length > 0)) { + if (u * 2 >= globalUvs.length) { + throw new RuntimeError(`UV index ${u} is out of bounds`); + } const ux = globalUvs.get(u * 2); const uy = globalUvs.get(u * 2 + 1); primitive.uvs.push(ux); diff --git a/lib/readLines.js b/lib/readLines.js index d4a4c1b..a566fb5 100644 --- a/lib/readLines.js +++ b/lib/readLines.js @@ -23,6 +23,15 @@ function readLines(path, callback) { const lineReader = readline.createInterface({ input : stream }); - lineReader.on('line', callback); + + const callbackWrapper = function(line) { + try { + callback(line); + } catch (error) { + reject(error); + } + }; + + lineReader.on('line', callbackWrapper); }); } diff --git a/package.json b/package.json index f991144..ffeb67d 100644 --- a/package.json +++ b/package.json @@ -38,13 +38,13 @@ "cloc": "^2.5.0", "coveralls": "^3.0.4", "eslint": "^6.0.0", - "eslint-config-cesium": "^7.0.0", + "eslint-config-cesium": "^8.0.0", "gulp": "^4.0.2", "jasmine": "^3.4.0", "jasmine-spec-reporter": "^4.2.1", "jsdoc": "^3.6.2", "nyc": "^14.1.1", - "open": "^6.3.0", + "open": "^7.0.0", "requirejs": "^2.3.6" }, "scripts": { diff --git a/specs/data/box-incomplete-attributes/box-incomplete-normals.obj b/specs/data/box-incomplete-attributes/box-incomplete-normals.obj new file mode 100644 index 0000000..328214c --- /dev/null +++ b/specs/data/box-incomplete-attributes/box-incomplete-normals.obj @@ -0,0 +1,36 @@ +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn 0.000000 0.000000 1.000000 +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/data/box-incomplete-attributes/box-incomplete-positions.obj b/specs/data/box-incomplete-attributes/box-incomplete-positions.obj new file mode 100644 index 0000000..789bb2d --- /dev/null +++ b/specs/data/box-incomplete-attributes/box-incomplete-positions.obj @@ -0,0 +1,34 @@ +o Cube +v -1.000000 -1.000000 1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/data/box-incomplete-attributes/box-incomplete-uvs.obj b/specs/data/box-incomplete-attributes/box-incomplete-uvs.obj new file mode 100644 index 0000000..fb87835 --- /dev/null +++ b/specs/data/box-incomplete-attributes/box-incomplete-uvs.obj @@ -0,0 +1,22 @@ +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/data/box-missing-attributes/box-missing-attributes.mtl b/specs/data/box-missing-attributes/box-missing-attributes.mtl new file mode 100644 index 0000000..abbc294 --- /dev/null +++ b/specs/data/box-missing-attributes/box-missing-attributes.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'box.blend' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-missing-attributes/box-missing-attributes.obj b/specs/data/box-missing-attributes/box-missing-attributes.obj new file mode 100644 index 0000000..1df6678 --- /dev/null +++ b/specs/data/box-missing-attributes/box-missing-attributes.obj @@ -0,0 +1,20 @@ +# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# www.blender.org +mtllib box-missing-attributes.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +usemtl Material +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/lib/loadObjSpec.js b/specs/lib/loadObjSpec.js index ca6f046..6fdf609 100644 --- a/specs/lib/loadObjSpec.js +++ b/specs/lib/loadObjSpec.js @@ -41,6 +41,10 @@ const objInvalidContentsPath = 'specs/data/box/box.mtl'; const objConcavePath = 'specs/data/concave/concave.obj'; const objUnnormalizedPath = 'specs/data/box-unnormalized/box-unnormalized.obj'; const objMixedAttributesPath = 'specs/data/box-mixed-attributes/box-mixed-attributes.obj'; +const objMissingAttributesPath = 'specs/data/box-missing-attributes/box-missing-attributes.obj'; +const objIncompletePositionsPath = 'specs/data/box-incomplete-attributes/box-incomplete-positions.obj'; +const objIncompleteNormalsPath = 'specs/data/box-incomplete-attributes/box-incomplete-normals.obj'; +const objIncompleteUvsPath = 'specs/data/box-incomplete-attributes/box-incomplete-uvs.obj'; const objInvalidPath = 'invalid.obj'; function getMeshes(data) { @@ -496,6 +500,44 @@ describe('loadObj', () => { } }); + it('ignores missing normals and uvs', async () => { + const data = await loadObj(objMissingAttributesPath, options); + const primitive = getPrimitives(data)[0]; + expect(primitive.positions.length).toBeGreaterThan(0); + expect(primitive.normals.length).toBe(0); + expect(primitive.uvs.length).toBe(0); + }); + + it('throws when position index is out of bounds', async () => { + let thrownError; + try { + await loadObj(objIncompletePositionsPath, options); + } catch (e) { + thrownError = e; + } + expect(thrownError).toEqual(new RuntimeError('Position index 1 is out of bounds')); + }); + + it('throws when normal index is out of bounds', async () => { + let thrownError; + try { + await loadObj(objIncompleteNormalsPath, options); + } catch (e) { + thrownError = e; + } + expect(thrownError).toEqual(new RuntimeError('Normal index 1 is out of bounds')); + }); + + it('throws when uv index is out of bounds', async () => { + let thrownError; + try { + await loadObj(objIncompleteUvsPath, options); + } catch (e) { + thrownError = e; + } + expect(thrownError).toEqual(new RuntimeError('UV index 1 is out of bounds')); + }); + it('throws when file has invalid contents', async () => { let thrownError; try {