diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 3b31283..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md index cf35100..df7736c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,14 @@ Change Log ========== +### 3.?.? - 2019-??-?? + +* Added back `inputUpAxis` and `outputUpAxis`. [#211](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/211) + +### 3.0.4 - 2019-07-22 + +* No longer printing texture decode warning if the diffuse and alpha textures are the same. [#205](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/205) + ### 3.0.3 2019-06-26 * Fixed parsing of negative face indices. [#191](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/191) diff --git a/bin/obj2gltf.js b/bin/obj2gltf.js index 47acf97..40ffe76 100644 --- a/bin/obj2gltf.js +++ b/bin/obj2gltf.js @@ -122,6 +122,18 @@ const argv = yargs describe : 'The glTF will be saved with the KHR_materials_unlit extension.', type : 'boolean', default : defaults.unlit + }, + inputUpAxis : { + describe: 'Up axis of the obj.', + choices: ['X', 'Y', 'Z'], + type: 'string', + default: 'Y' + }, + outputUpAxis : { + describe: 'Up axis of the converted glTF.', + choices: ['X', 'Y', 'Z'], + type: 'string', + default: 'Y' } }).parse(args); @@ -167,7 +179,9 @@ const options = { specularGlossiness : argv.specularGlossiness, unlit : argv.unlit, overridingTextures : overridingTextures, - outputDirectory : outputDirectory + outputDirectory : outputDirectory, + inputUpAxis : argv.inputUpAxis, + outputUpAxis : argv.outputUpAxis }; console.time('Total'); diff --git a/lib/loadMtl.js b/lib/loadMtl.js index dc42545..e220536 100644 --- a/lib/loadMtl.js +++ b/lib/loadMtl.js @@ -387,6 +387,10 @@ function createDiffuseAlphaTexture(diffuseTexture, alphaTexture, options) { return diffuseTexture; } + if (diffuseTexture === alphaTexture) { + return diffuseTexture; + } + if (!defined(diffuseTexture.pixels) || !defined(alphaTexture.pixels)) { options.logger('Could not get decoded texture data for ' + diffuseTexture.path + ' or ' + alphaTexture.path + '. The material will be created without an alpha texture.'); return diffuseTexture; diff --git a/lib/loadObj.js b/lib/loadObj.js index 392d479..c46b13b 100644 --- a/lib/loadObj.js +++ b/lib/loadObj.js @@ -8,6 +8,7 @@ const loadMtl = require('./loadMtl'); const outsideDirectory = require('./outsideDirectory'); const readLines = require('./readLines'); +const Axis = Cesium.Axis; const Cartesian3 = Cesium.Cartesian3; const ComponentDatatype = Cesium.ComponentDatatype; const CoplanarPolygonGeometryLibrary = Cesium.CoplanarPolygonGeometryLibrary; @@ -16,6 +17,7 @@ const defined = Cesium.defined; const PolygonPipeline = Cesium.PolygonPipeline; const RuntimeError = Cesium.RuntimeError; const WindingOrder = Cesium.WindingOrder; +const Matrix4 = Cesium.Matrix4; module.exports = loadObj; @@ -47,6 +49,8 @@ const normalPattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\ const uvPattern = /vt( +[\d|\.|\+|\-|e|E]+)( +[\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 scratchCartesian = new Cartesian3(); + /** * Parse an obj file. * @@ -57,6 +61,8 @@ const facePattern = /(-?\d+)\/?(-?\d*)\/?(-?\d*)/g; * @private */ function loadObj(objPath, options) { + const axisTransform = getAxisTransform(options.inputUpAxis, options.outputUpAxis); + // Global store of vertex attributes listed in the obj file let globalPositions = new ArrayStorage(ComponentDatatype.FLOAT); let globalNormals = new ArrayStorage(ComponentDatatype.FLOAT); @@ -354,9 +360,16 @@ function loadObj(objPath, options) { const mtllibLine = line.substring(7).trim(); mtlPaths = mtlPaths.concat(getMtlPaths(mtllibLine)); } else if ((result = vertexPattern.exec(line)) !== null) { - globalPositions.push(parseFloat(result[1])); - globalPositions.push(parseFloat(result[2])); - globalPositions.push(parseFloat(result[3])); + const position = scratchCartesian; + position.x = parseFloat(result[1]); + position.y = parseFloat(result[2]); + position.z = parseFloat(result[3]); + if (defined(axisTransform)) { + Matrix4.multiplyByPoint(axisTransform, position, position); + } + globalPositions.push(position.x); + globalPositions.push(position.y); + globalPositions.push(position.z); } else if ((result = normalPattern.exec(line) ) !== null) { const normal = Cartesian3.fromElements(parseFloat(result[1]), parseFloat(result[2]), parseFloat(result[3]), scratchNormal); if (Cartesian3.equals(normal, Cartesian3.ZERO)) { @@ -364,6 +377,9 @@ function loadObj(objPath, options) { } else { Cartesian3.normalize(normal, normal); } + if (defined(axisTransform)) { + Matrix4.multiplyByPointAsVector(axisTransform, normal, normal); + } globalNormals.push(normal.x); globalNormals.push(normal.y); globalNormals.push(normal.z); @@ -625,3 +641,19 @@ function cleanNodes(nodes) { setDefaults(nodes); return nodes; } + +function getAxisTransform(inputUpAxis, outputUpAxis) { + if (inputUpAxis === 'X' && outputUpAxis === 'Y') { + return Axis.X_UP_TO_Y_UP; + } else if (inputUpAxis === 'X' && outputUpAxis === 'Z') { + return Axis.X_UP_TO_Z_UP; + } else if (inputUpAxis === 'Y' && outputUpAxis === 'X') { + return Axis.Y_UP_TO_X_UP; + } else if (inputUpAxis === 'Y' && outputUpAxis === 'Z') { + return Axis.Y_UP_TO_Z_UP; + } else if (inputUpAxis === 'Z' && outputUpAxis === 'X') { + return Axis.Z_UP_TO_X_UP; + } else if (inputUpAxis === 'Z' && outputUpAxis === 'Y') { + return Axis.Z_UP_TO_Y_UP; + } +} diff --git a/lib/obj2gltf.js b/lib/obj2gltf.js index 0f5a66c..cdb57e8 100644 --- a/lib/obj2gltf.js +++ b/lib/obj2gltf.js @@ -34,6 +34,8 @@ module.exports = obj2gltf; * @param {String} [options.overridingTextures.baseColorTexture] Path to the baseColor/diffuse texture. * @param {String} [options.overridingTextures.emissiveTexture] Path to the emissive texture. * @param {String} [options.overridingTextures.alphaTexture] Path to the alpha texture. + * @param {String} [options.inputUpAxis='Y'] Up axis of the obj. Choices are 'X', 'Y', and 'Z'. + * @param {String} [options.outputUpAxis='Y'] Up axis of the converted glTF. Choices are 'X', 'Y', and 'Z'. * @param {Logger} [options.logger] A callback function for handling logged messages. Defaults to console.log. * @param {Writer} [options.writer] A callback function that writes files that are saved as separate resources. * @param {String} [options.outputDirectory] Output directory for writing separate resources when options.writer is not defined. @@ -54,6 +56,8 @@ function obj2gltf(objPath, options) { options.overridingTextures = defaultValue(options.overridingTextures, defaultValue.EMPTY_OBJECT); options.logger = defaultValue(options.logger, getDefaultLogger()); options.writer = defaultValue(options.writer, getDefaultWriter(options.outputDirectory)); + options.inputUpAxis = defaultValue(options.inputUpAxis, defaults.inputUpAxis); + options.outputUpAxis = defaultValue(options.outputUpAxis, defaults.outputUpAxis); if (!defined(objPath)) { throw new DeveloperError('objPath is required'); @@ -164,7 +168,19 @@ obj2gltf.defaults = { * @type Boolean * @default false */ - unlit : false + unlit : false, + /** + * Gets or sets the up axis of the obj. + * @type String + * @default 'Y' + */ + inputUpAxis: 'Y', + /** + * Gets or sets the up axis of the converted glTF. + * @type String + * @default 'Y' + */ + outputUpAxis: 'Y' }; /** diff --git a/package.json b/package.json index 430602f..f991144 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obj2gltf", - "version": "3.0.3", + "version": "3.0.4", "description": "Convert OBJ model format to glTF", "license": "Apache-2.0", "contributors": [ @@ -32,7 +32,7 @@ "jpeg-js": "^0.3.5", "mime": "^2.4.4", "pngjs": "^3.4.0", - "yargs": "^13.2.4" + "yargs": "^14.2.0" }, "devDependencies": { "cloc": "^2.5.0", diff --git a/specs/lib/loadMtlSpec.js b/specs/lib/loadMtlSpec.js index 2af7f0a..fb46c90 100644 --- a/specs/lib/loadMtlSpec.js +++ b/specs/lib/loadMtlSpec.js @@ -321,6 +321,20 @@ describe('loadMtl', () => { expect(material.alphaMode).toBe('BLEND'); expect(material.doubleSided).toBe(true); }); + + it('uses diffuse texture if diffuse and alpha are the same', () => { + options.metallicRoughness = true; + + const material = loadMtl._createMaterial({ + diffuseTexture : diffuseTexture, + alphaTexture : diffuseTexture + }, options); + + const pbr = material.pbrMetallicRoughness; + expect(pbr.baseColorTexture).toBe(diffuseTexture); + expect(material.alphaMode).toBe('BLEND'); + expect(material.doubleSided).toBe(true); + }); }); describe('specularGlossiness', () => { @@ -416,5 +430,19 @@ describe('loadMtl', () => { expect(material.alphaMode).toBe('BLEND'); expect(material.doubleSided).toBe(true); }); + + it('uses diffuse texture if diffuse and alpha are the same', () => { + options.specularGlossiness = true; + + const material = loadMtl._createMaterial({ + diffuseTexture : diffuseTexture, + alphaTexture : diffuseTexture + }, options); + + const pbr = material.extensions.KHR_materials_pbrSpecularGlossiness; + expect(pbr.diffuseTexture).toEqual(diffuseTexture); + expect(material.alphaMode).toBe('BLEND'); + expect(material.doubleSided).toBe(true); + }); }); }); diff --git a/specs/lib/loadObjSpec.js b/specs/lib/loadObjSpec.js index 7cb3172..47d1570 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 objRotatedUrl = 'specs/data/box-rotated/box-rotated.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'; @@ -456,6 +457,46 @@ describe('loadObj', () => { expect(primitives[3].indices.length).toBe(6); // 2 faces }); + function getFirstPosition(data) { + const primitive = getPrimitives(data)[0]; + return new Cartesian3(primitive.positions.get(0), primitive.positions.get(1), primitive.positions.get(2)); + } + + function getFirstNormal(data) { + const primitive = getPrimitives(data)[0]; + return new Cartesian3(primitive.normals.get(0), primitive.normals.get(1), primitive.normals.get(2)); + } + + async function checkAxisConversion(inputUpAxis, outputUpAxis, position, normal) { + const sameAxis = (inputUpAxis === outputUpAxis); + options.inputUpAxis = inputUpAxis; + options.outputUpAxis = outputUpAxis; + const data = await loadObj(objRotatedUrl, options); + const rotatedPosition = getFirstPosition(data); + const rotatedNormal = getFirstNormal(data); + if (sameAxis) { + expect(rotatedPosition).toEqual(position); + expect(rotatedNormal).toEqual(normal); + } else { + expect(rotatedPosition).not.toEqual(position); + expect(rotatedNormal).not.toEqual(normal); + } + } + + it('performs up axis conversion', async () => { + const data = await loadObj(objRotatedUrl, options); + const position = getFirstPosition(data); + const normal = getFirstNormal(data); + + const axes = ['X', 'Y', 'Z']; + const axesLength = axes.length; + for (let i = 0; i < axesLength; ++i) { + for (let j = 0; j < axesLength; ++j) { + await checkAxisConversion(axes[i], axes[j], position, normal); + } + } + }); + it('does not add missing normals and uvs', async() => { const data = await loadObj(objMissingAttributesPath, options); const primitive = getPrimitives(data)[0];