diff --git a/CHANGES.md b/CHANGES.md index c9b8b60..b360198 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ Change Log ========== +### 3.0.3 2019-??-?? + +* Fixed parsing obj files that contain tabs. + ### 3.0.2 2019-03-21 * Fixed a crash when saving separate resources that would exceed the Node buffer size limit. [#173](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/173) diff --git a/lib/loadMtl.js b/lib/loadMtl.js index dc42545..c2ecd55 100644 --- a/lib/loadMtl.js +++ b/lib/loadMtl.js @@ -96,7 +96,7 @@ function loadMtl(mtlPath, options) { if (/^newmtl/i.test(line)) { const name = line.substring(7).trim(); createMaterial(name); - } else if (/^Ka /i.test(line)) { + } else if (/^Ka\s+/i.test(line)) { values = line.substring(3).trim().split(' '); material.ambientColor = [ parseFloat(values[0]), @@ -104,7 +104,7 @@ function loadMtl(mtlPath, options) { parseFloat(values[2]), 1.0 ]; - } else if (/^Ke /i.test(line)) { + } else if (/^Ke\s+/i.test(line)) { values = line.substring(3).trim().split(' '); material.emissiveColor = [ parseFloat(values[0]), @@ -112,7 +112,7 @@ function loadMtl(mtlPath, options) { parseFloat(values[2]), 1.0 ]; - } else if (/^Kd /i.test(line)) { + } else if (/^Kd\s+/i.test(line)) { values = line.substring(3).trim().split(' '); material.diffuseColor = [ parseFloat(values[0]), @@ -120,7 +120,7 @@ function loadMtl(mtlPath, options) { parseFloat(values[2]), 1.0 ]; - } else if (/^Ks /i.test(line)) { + } else if (/^Ks\s+/i.test(line)) { values = line.substring(3).trim().split(' '); material.specularColor = [ parseFloat(values[0]), @@ -128,40 +128,40 @@ function loadMtl(mtlPath, options) { parseFloat(values[2]), 1.0 ]; - } else if (/^Ns /i.test(line)) { + } else if (/^Ns\s+/i.test(line)) { value = line.substring(3).trim(); material.specularShininess = parseFloat(value); - } else if (/^d /i.test(line)) { + } else if (/^d\s+/i.test(line)) { value = line.substring(2).trim(); material.alpha = correctAlpha(parseFloat(value)); - } else if (/^Tr /i.test(line)) { + } else if (/^Tr\s+/i.test(line)) { value = line.substring(3).trim(); material.alpha = correctAlpha(1.0 - parseFloat(value)); - } else if (/^map_Ka /i.test(line)) { + } else if (/^map_Ka\s+/i.test(line)) { if (!defined(overridingAmbientTexture)) { material.ambientTexture = normalizeTexturePath(line.substring(7).trim(), mtlDirectory); } - } else if (/^map_Ke /i.test(line)) { + } else if (/^map_Ke\s+/i.test(line)) { if (!defined(overridingEmissiveTexture)) { material.emissiveTexture = normalizeTexturePath(line.substring(7).trim(), mtlDirectory); } - } else if (/^map_Kd /i.test(line)) { + } else if (/^map_Kd\s+/i.test(line)) { if (!defined(overridingDiffuseTexture)) { material.diffuseTexture = normalizeTexturePath(line.substring(7).trim(), mtlDirectory); } - } else if (/^map_Ks /i.test(line)) { + } else if (/^map_Ks\s+/i.test(line)) { if (!defined(overridingSpecularTexture)) { material.specularTexture = normalizeTexturePath(line.substring(7).trim(), mtlDirectory); } - } else if (/^map_Ns /i.test(line)) { + } else if (/^map_Ns\s+/i.test(line)) { if (!defined(overridingSpecularShininessTexture)) { material.specularShininessTexture = normalizeTexturePath(line.substring(7).trim(), mtlDirectory); } - } else if (/^map_Bump /i.test(line)) { + } else if (/^map_Bump\s+/i.test(line)) { if (!defined(overridingNormalTexture)) { material.normalTexture = normalizeTexturePath(line.substring(9).trim(), mtlDirectory); } - } else if (/^map_d /i.test(line)) { + } else if (/^map_d\s+/i.test(line)) { if (!defined(overridingAlphaTexture)) { material.alphaTexture = normalizeTexturePath(line.substring(6).trim(), mtlDirectory); } diff --git a/lib/loadObj.js b/lib/loadObj.js index 2e082e0..ba198d2 100644 --- a/lib/loadObj.js +++ b/lib/loadObj.js @@ -42,10 +42,11 @@ function Primitive() { } // OBJ regex patterns are modified from ThreeJS (https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/OBJLoader.js) -const vertexPattern = /v( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // v float float float -const normalPattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // vn float float float -const uvPattern = /vt( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // vt float float +const vertexPattern = /v(\s+[\d|\.|\+|\-|e|E]+)(\s+[\d|\.|\+|\-|e|E]+)(\s+[\d|\.|\+|\-|e|E]+)/; // v float float float +const normalPattern = /vn(\s+[\d|\.|\+|\-|e|E]+)(\s+[\d|\.|\+|\-|e|E]+)(\s+[\d|\.|\+|\-|e|E]+)/; // vn float float float +const uvPattern = /vt(\s+[\d|\.|\+|\-|e|E]+)(\s+[\d|\.|\+|\-|e|E]+)/; // vt float float const facePattern = /(-?\d+)\/?(-?\d*)\/?(-?\d*)/g; // for any face format "f v", "f v/v", "f v//v", "f v/v/v" +const faceStartPattern = /f\s+/; /** * Parse an obj file. @@ -356,11 +357,11 @@ function loadObj(objPath, options) { // Because face lines can contain n vertices, we use a line buffer in case the face data spans multiple lines. // If there's a line continuation don't create face yet if (line.slice(-1) === '\\') { - lineBuffer += line.substring(0, line.length-1); + lineBuffer += line.slice(0, -1); return; } lineBuffer += line; - if (lineBuffer.substring(0, 2) === 'f ') { + if (faceStartPattern.test(lineBuffer)) { while ((result = facePattern.exec(lineBuffer)) !== null) { faceVertices.push(result[0]); facePositions.push(result[1]); @@ -402,7 +403,7 @@ function loadObj(objPath, options) { function getMtlPaths(mtllibLine) { // Handle paths with spaces. E.g. mtllib my material file.mtl const mtlPaths = []; - const splits = mtllibLine.split(' '); + const splits = mtllibLine.split(/\s/); const length = splits.length; let startIndex = 0; for (let i = 0; i < length; ++i) { diff --git a/specs/data/box-tabs/box-tabs.mtl b/specs/data/box-tabs/box-tabs.mtl new file mode 100644 index 0000000..2f6c91e --- /dev/null +++ b/specs/data/box-tabs/box-tabs.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'None' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.100000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.100000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-tabs/box-tabs.obj b/specs/data/box-tabs/box-tabs.obj new file mode 100644 index 0000000..4546e11 --- /dev/null +++ b/specs/data/box-tabs/box-tabs.obj @@ -0,0 +1,46 @@ +#Blender v2.78 (sub 0) OBJ File: '' +# www.blender.org +mtllib box-tabs.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 +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 +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 7a84397..cb561dd 100644 --- a/specs/lib/loadObjSpec.js +++ b/specs/lib/loadObjSpec.js @@ -10,6 +10,7 @@ const clone = Cesium.clone; const RuntimeError = Cesium.RuntimeError; const objPath = 'specs/data/box/box.obj'; +const objTabsPath = 'specs/data/box-tabs/box-tabs.obj'; const objNormalsPath = 'specs/data/box-normals/box-normals.obj'; const objUvsPath = 'specs/data/box-uvs/box-uvs.obj'; const objPositionsOnlyPath = 'specs/data/box-positions-only/box-positions-only.obj'; @@ -102,6 +103,25 @@ describe('loadObj', () => { expect(primitive.material).toBe('Material'); }); + it('loads obj with tabs', async () => { + const data = await loadObj(objTabsPath, options); + const primitives = getPrimitives(data); + const primitive = primitives[0]; + + expect(primitive.positions.length / 3).toBe(24); + expect(primitive.normals.length / 3).toBe(24); + expect(primitive.uvs.length / 2).toBe(24); + expect(primitive.indices.length).toBe(36); + expect(primitive.material).toBe('Material'); + + const material = data.materials[0]; + const pbr = material.pbrMetallicRoughness; + expect(pbr.baseColorFactor).toEqual([0.64, 0.64, 0.64, 1.0]); + expect(pbr.metallicFactor).toBe(0.0); + expect(CesiumMath.equalsEpsilon(pbr.roughnessFactor, 0.903921569, CesiumMath.EPSILON7)).toBe(true); + expect(material.emissiveFactor).toEqual([0.0, 0.0, 0.1]); + }); + it('loads obj with normals', async () => { const data = await loadObj(objNormalsPath, options); const primitive = getPrimitives(data)[0];