Run prettier

This commit is contained in:
Sean Lilley 2021-08-02 11:31:59 -04:00
parent bd4b7320fa
commit dc3ec3074f
24 changed files with 4965 additions and 4137 deletions

View File

@ -1,147 +1,146 @@
Change Log # Change Log
==========
### 3.?.? - 2021-??-?? ### 3.?.? - 2021-??-??
* Removed `minFilter` and `magFilter` from generated samplers so that runtime engines can use their preferred texture filtering. [#240](https://github.com/CesiumGS/obj2gltf/pull/240) - Removed `minFilter` and `magFilter` from generated samplers so that runtime engines can use their preferred texture filtering. [#240](https://github.com/CesiumGS/obj2gltf/pull/240)
* Triangle winding order sanitization is longer done by default. Use the `--triangle-winding-order-sanitization` option. [#236](https://github.com/CesiumGS/obj2gltf/pull/236) - Triangle winding order sanitization is longer done by default. Use the `--triangle-winding-order-sanitization` option. [#236](https://github.com/CesiumGS/obj2gltf/pull/236)
### 3.1.1 - 2021-06-22 ### 3.1.1 - 2021-06-22
* Fixed security warnings by updating outdated npm dependencies. [#254](https://github.com/CesiumGS/obj2gltf/pull/254) - Fixed security warnings by updating outdated npm dependencies. [#254](https://github.com/CesiumGS/obj2gltf/pull/254)
### 3.1.0 - 2020-03-13 ### 3.1.0 - 2020-03-13
* Added back `inputUpAxis` and `outputUpAxis`. [#211](https://github.com/CesiumGS/obj2gltf/pull/211) - Added back `inputUpAxis` and `outputUpAxis`. [#211](https://github.com/CesiumGS/obj2gltf/pull/211)
* Fixed handling of mtl and texture absolute paths. [#219](https://github.com/CesiumGS/obj2gltf/pull/219) - Fixed handling of mtl and texture absolute paths. [#219](https://github.com/CesiumGS/obj2gltf/pull/219)
* Fixed specular image not being decoded when referenced by other textures. [#217](https://github.com/CesiumGS/obj2gltf/pull/217) - Fixed specular image not being decoded when referenced by other textures. [#217](https://github.com/CesiumGS/obj2gltf/pull/217)
* Fixed parsing faces that reference non-existing attributes. [#218](https://github.com/CesiumGS/obj2gltf/pull/218) - Fixed parsing faces that reference non-existing attributes. [#218](https://github.com/CesiumGS/obj2gltf/pull/218)
### 3.0.4 - 2019-07-22 ### 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/CesiumGS/obj2gltf/pull/205) - No longer printing texture decode warning if the diffuse and alpha textures are the same. [#205](https://github.com/CesiumGS/obj2gltf/pull/205)
### 3.0.3 2019-06-26 ### 3.0.3 2019-06-26
* Fixed parsing of negative face indices. [#191](https://github.com/CesiumGS/obj2gltf/pull/191) - Fixed parsing of negative face indices. [#191](https://github.com/CesiumGS/obj2gltf/pull/191)
### 3.0.2 2019-03-21 ### 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/CesiumGS/obj2gltf/pull/173) - Fixed a crash when saving separate resources that would exceed the Node buffer size limit. [#173](https://github.com/CesiumGS/obj2gltf/pull/173)
### 3.0.1 2019-03-08 ### 3.0.1 2019-03-08
* Fixed handling of materials that don't have names. [#173](https://github.com/CesiumGS/obj2gltf/pull/173) - Fixed handling of materials that don't have names. [#173](https://github.com/CesiumGS/obj2gltf/pull/173)
### 3.0.0 2018-12-05 ### 3.0.0 2018-12-05
* Breaking changes - Breaking changes
* The `--materialsCommon` flag has been removed. Use `--unlit` instead which uses the `KHR_materials_unlit` extension. [#152](https://github.com/CesiumGS/obj2gltf/pull/152) - The `--materialsCommon` flag has been removed. Use `--unlit` instead which uses the `KHR_materials_unlit` extension. [#152](https://github.com/CesiumGS/obj2gltf/pull/152)
### 2.3.2 2018-11-02 ### 2.3.2 2018-11-02
* Improved handling of primitives with different attributes using the same material. Materials are now duplicated. [#162](https://github.com/CesiumGS/obj2gltf/pull/162) - Improved handling of primitives with different attributes using the same material. Materials are now duplicated. [#162](https://github.com/CesiumGS/obj2gltf/pull/162)
* Fixed a bug where primitives without texture coordinates could use materials containing textures. Those textures are now removed. [#162](https://github.com/CesiumGS/obj2gltf/pull/162) - Fixed a bug where primitives without texture coordinates could use materials containing textures. Those textures are now removed. [#162](https://github.com/CesiumGS/obj2gltf/pull/162)
* Improved parsing of faces with mismatching attributes. [#161](https://github.com/CesiumGS/obj2gltf/pull/161) - Improved parsing of faces with mismatching attributes. [#161](https://github.com/CesiumGS/obj2gltf/pull/161)
### 2.3.1 2018-10-16 ### 2.3.1 2018-10-16
* Improved parsing models with concave or n-sided faces. [#157](https://github.com/CesiumGS/obj2gltf/pull/157) - Improved parsing models with concave or n-sided faces. [#157](https://github.com/CesiumGS/obj2gltf/pull/157)
* Fixed handling of objs with interleaved materials. [#155](https://github.com/CesiumGS/obj2gltf/pull/155) - Fixed handling of objs with interleaved materials. [#155](https://github.com/CesiumGS/obj2gltf/pull/155)
### 2.3.0 2018-09-19 ### 2.3.0 2018-09-19
* Fixed handling of objs with mismatching attribute layouts. [#153](https://github.com/CesiumGS/obj2gltf/pull/153) - Fixed handling of objs with mismatching attribute layouts. [#153](https://github.com/CesiumGS/obj2gltf/pull/153)
* Fixed normalization of Windows paths when running the converter on Linux. [#150](https://github.com/CesiumGS/obj2gltf/pull/150) - Fixed normalization of Windows paths when running the converter on Linux. [#150](https://github.com/CesiumGS/obj2gltf/pull/150)
* Added ability to use the first material in the mtl file when the obj is missing `usemtl`. [#133](https://github.com/CesiumGS/obj2gltf/pull/133) - Added ability to use the first material in the mtl file when the obj is missing `usemtl`. [#133](https://github.com/CesiumGS/obj2gltf/pull/133)
* Fixed handling of unnormalized input normals. [#136](https://github.com/CesiumGS/obj2gltf/pull/136) - Fixed handling of unnormalized input normals. [#136](https://github.com/CesiumGS/obj2gltf/pull/136)
### 2.2.0 2018-01-29 ### 2.2.0 2018-01-29
* Fixed handling of materials where the diffuse and ambient texture are the same. [#127](https://github.com/CesiumGS/obj2gltf/pull/127) - Fixed handling of materials where the diffuse and ambient texture are the same. [#127](https://github.com/CesiumGS/obj2gltf/pull/127)
* Added ability to load alpha textures. [#124](https://github.com/CesiumGS/obj2gltf/pull/124) - Added ability to load alpha textures. [#124](https://github.com/CesiumGS/obj2gltf/pull/124)
* Fixed handling of `usemtl` when appearing before an `o` or `g` token. [#123](https://github.com/CesiumGS/obj2gltf/pull/123) - Fixed handling of `usemtl` when appearing before an `o` or `g` token. [#123](https://github.com/CesiumGS/obj2gltf/pull/123)
* Fixed output name when running from the command line. [#126](https://github.com/CesiumGS/obj2gltf/pull/126) - Fixed output name when running from the command line. [#126](https://github.com/CesiumGS/obj2gltf/pull/126)
### 2.1.0 2017-12-28 ### 2.1.0 2017-12-28
* Fixed loading faces that contain less than 3 vertices. [#120](https://github.com/CesiumGS/obj2gltf/pull/120) - Fixed loading faces that contain less than 3 vertices. [#120](https://github.com/CesiumGS/obj2gltf/pull/120)
* Attempt to load missing materials and textures from within the same directory as the obj. [#117](https://github.com/CesiumGS/obj2gltf/pull/117) - Attempt to load missing materials and textures from within the same directory as the obj. [#117](https://github.com/CesiumGS/obj2gltf/pull/117)
* Fixed loading mtllib paths that contain spaces. [#116](https://github.com/CesiumGS/obj2gltf/pull/116) - Fixed loading mtllib paths that contain spaces. [#116](https://github.com/CesiumGS/obj2gltf/pull/116)
* Fixed checking for transparency when the diffuse texture is used in another texture slot. [#115](https://github.com/CesiumGS/obj2gltf/pull/115) - Fixed checking for transparency when the diffuse texture is used in another texture slot. [#115](https://github.com/CesiumGS/obj2gltf/pull/115)
* Fixed parsing mtl textures that contain texture map options. [#109](https://github.com/CesiumGS/obj2gltf/pull/109) - Fixed parsing mtl textures that contain texture map options. [#109](https://github.com/CesiumGS/obj2gltf/pull/109)
* Added back support for the `CONSTANT` technique when a model uses the `KHR_materials_common` extension and has no normals. [#108](https://github.com/CesiumGS/obj2gltf/pull/108) - Added back support for the `CONSTANT` technique when a model uses the `KHR_materials_common` extension and has no normals. [#108](https://github.com/CesiumGS/obj2gltf/pull/108)
* Improved handling of materials with alpha. If the alpha value is 0.0 it is now treated as 1.0. [#107](https://github.com/CesiumGS/obj2gltf/pull/107) - Improved handling of materials with alpha. If the alpha value is 0.0 it is now treated as 1.0. [#107](https://github.com/CesiumGS/obj2gltf/pull/107)
### 2.0.0 2017-08-11 ### 2.0.0 2017-08-11
* Breaking changes - Breaking changes
* Obj models now convert to glTF 2.0. Possible material profiles are `metallicRoughness`, `specGlossiness` (using the `KHR_materials_pbrSpecularGlossiness` extension), and `materialsCommon` (using the `KHR_materials_common` extension). - Obj models now convert to glTF 2.0. Possible material profiles are `metallicRoughness`, `specGlossiness` (using the `KHR_materials_pbrSpecularGlossiness` extension), and `materialsCommon` (using the `KHR_materials_common` extension).
* Removed `gltf-pipeline` dependency. The following options have been removed: `compress`, `optimize`, `generateNormals`, `optimizeForCesium`, `ao`, and `bypassPipeline`. - Removed `gltf-pipeline` dependency. The following options have been removed: `compress`, `optimize`, `generateNormals`, `optimizeForCesium`, `ao`, and `bypassPipeline`.
* Removed `inputUpAxis` and `outputUpAxis`. This stage will be incorporated into `gltf-pipeline` instead. - Removed `inputUpAxis` and `outputUpAxis`. This stage will be incorporated into `gltf-pipeline` instead.
* `obj2gltf` no longer takes a `gltfPath` argument and saves a glTF file. Instead it returns a promise that resolves to the glTF JSON or glb buffer. - `obj2gltf` no longer takes a `gltfPath` argument and saves a glTF file. Instead it returns a promise that resolves to the glTF JSON or glb buffer.
### 1.3.0 2017-08-11 ### 1.3.0 2017-08-11
* Fixed parsing models with concave or n-sided faces. [#85](https://github.com/CesiumGS/obj2gltf/pull/85) - Fixed parsing models with concave or n-sided faces. [#85](https://github.com/CesiumGS/obj2gltf/pull/85)
* Fixed parsing models with line breaks. [#85](https://github.com/CesiumGS/obj2gltf/pull/85) - Fixed parsing models with line breaks. [#85](https://github.com/CesiumGS/obj2gltf/pull/85)
### 1.2.0 2017-07-11 ### 1.2.0 2017-07-11
* Change texture sampling to use `NEAREST_MIPMAP_LINEAR` by default. [#83](https://github.com/CesiumGS/obj2gltf/pull/83). - Change texture sampling to use `NEAREST_MIPMAP_LINEAR` by default. [#83](https://github.com/CesiumGS/obj2gltf/pull/83).
* Fixed lighting when generating normals. [#89](https://github.com/CesiumGS/obj2gltf/pull/89) - Fixed lighting when generating normals. [#89](https://github.com/CesiumGS/obj2gltf/pull/89)
### 1.1.1 2017-04-25 ### 1.1.1 2017-04-25
* Fixed `CHANGES.md` formatting. - Fixed `CHANGES.md` formatting.
### 1.1.0 2017-04-25 ### 1.1.0 2017-04-25
* Added ability to convert the up-axis of the obj model. [#68](https://github.com/CesiumGS/obj2gltf/pull/68) - Added ability to convert the up-axis of the obj model. [#68](https://github.com/CesiumGS/obj2gltf/pull/68)
* Fixed issues with an extra .bin file being saved when using `--separate`. [#62](https://github.com/CesiumGS/obj2gltf/pull/62) - Fixed issues with an extra .bin file being saved when using `--separate`. [#62](https://github.com/CesiumGS/obj2gltf/pull/62)
* Fixed issue where an ambient color of `[1, 1, 1]` overly brightens the converted model. [#70](https://github.com/CesiumGS/obj2gltf/pull/70) - Fixed issue where an ambient color of `[1, 1, 1]` overly brightens the converted model. [#70](https://github.com/CesiumGS/obj2gltf/pull/70)
### 1.0.0 2017-04-13 ### 1.0.0 2017-04-13
* Breaking changes - Breaking changes
* To use `obj2gltf` as a library, call `require('obj2gltf')(input, output, options)`. The previous calling code was `require('obj2gltf').convert(input, output, options)`. - To use `obj2gltf` as a library, call `require('obj2gltf')(input, output, options)`. The previous calling code was `require('obj2gltf').convert(input, output, options)`.
* Many library options and command-line parameters have been renamed. - Many library options and command-line parameters have been renamed.
* Project cleanup. [#49](https://github.com/CesiumGS/obj2gltf/pull/49) - Project cleanup. [#49](https://github.com/CesiumGS/obj2gltf/pull/49)
* Speed improvements, especially for larger models. - Speed improvements, especially for larger models.
* Preserves the objects and groups in the obj. - Preserves the objects and groups in the obj.
* Added documentation and tests. - Added documentation and tests.
* Material fixes. - Material fixes.
### 0.1.7 2017-01-06 ### 0.1.7 2017-01-06
* Update gltf-pipeline to 0.1.0-alpha9 - Update gltf-pipeline to 0.1.0-alpha9
* Added command to generate documentation (npm run jsdoc) - Added command to generate documentation (npm run jsdoc)
### 0.1.6 2016-09-07 ### 0.1.6 2016-09-07
* Changed obj2gltf.js line endings from CRLF to LF in npm package. - Changed obj2gltf.js line endings from CRLF to LF in npm package.
### 0.1.5 2016-08-26 ### 0.1.5 2016-08-26
* Fixed incorrect parameter to the gltf-pipeline. - Fixed incorrect parameter to the gltf-pipeline.
### 0.1.4 2016-08-25 ### 0.1.4 2016-08-25
* Added compression flag for quantizing positions, compressing texture coordinates, and oct-encoding normals. - Added compression flag for quantizing positions, compressing texture coordinates, and oct-encoding normals.
### 0.1.3 - 2016-08-08 ### 0.1.3 - 2016-08-08
* Fixed a bug causing models with no mtl file to not convert. - Fixed a bug causing models with no mtl file to not convert.
### 0.1.2 - 2016-07-25 ### 0.1.2 - 2016-07-25
* Converted the API to now use promises instead of callbacks. [#21](https://github.com/CesiumGS/OBJ2GLTF/pull/21) - Converted the API to now use promises instead of callbacks. [#21](https://github.com/CesiumGS/OBJ2GLTF/pull/21)
* Added the ability to optimize the converted glTF for CesiumJS by using the sun as a default light source. - Added the ability to optimize the converted glTF for CesiumJS by using the sun as a default light source.
### 0.1.1 - 2016-07-21 ### 0.1.1 - 2016-07-21
* Updated to use gltf-pipeline 0.1.0-alpha2. - Updated to use gltf-pipeline 0.1.0-alpha2.
### 0.1.0 - 2016-07-20 ### 0.1.0 - 2016-07-20
* Initial release. - Initial release.

View File

@ -202,9 +202,7 @@ Copyright 2016-2020 Cesium GS, Inc. and Contributors
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
# Third-Party Code
Third-Party Code
================
obj2gltf includes the following third-party code. obj2gltf includes the following third-party code.

View File

@ -5,6 +5,7 @@ Convert OBJ assets to [glTF](https://www.khronos.org/gltf) 2.0.
## Getting Started ## Getting Started
Install [Node.js](https://nodejs.org/en/) if you don't already have it, and then: Install [Node.js](https://nodejs.org/en/) if you don't already have it, and then:
``` ```
npm install -g obj2gltf npm install -g obj2gltf
``` ```
@ -22,26 +23,24 @@ npm install -g obj2gltf
#### Converting an obj model to gltf: #### Converting an obj model to gltf:
```javascript ```javascript
const obj2gltf = require('obj2gltf'); const obj2gltf = require("obj2gltf");
const fs = require('fs'); const fs = require("fs");
obj2gltf('model.obj') obj2gltf("model.obj").then(function (gltf) {
.then(function(gltf) {
const data = Buffer.from(JSON.stringify(gltf)); const data = Buffer.from(JSON.stringify(gltf));
fs.writeFileSync('model.gltf', data); fs.writeFileSync("model.gltf", data);
}); });
``` ```
#### Converting an obj model to glb #### Converting an obj model to glb
```javascript ```javascript
const obj2gltf = require('obj2gltf'); const obj2gltf = require("obj2gltf");
const fs = require('fs'); const fs = require("fs");
const options = { const options = {
binary : true binary: true,
} };
obj2gltf('model.obj', options) obj2gltf("model.obj", options).then(function (glb) {
.then(function(glb) { fs.writeFileSync("model.glb", glb);
fs.writeFileSync('model.glb', glb);
}); });
``` ```
@ -52,9 +51,9 @@ materials.
There are three shading models supported by `obj2gltf`: There are three shading models supported by `obj2gltf`:
* Metallic roughness PBR - Metallic roughness PBR
* Specular glossiness PBR (via `KHR_materials_pbrSpecularGlossiness` extension) - Specular glossiness PBR (via `KHR_materials_pbrSpecularGlossiness` extension)
* Unlit materials (via `KHR_materials_unlit` extension) - Unlit materials (via `KHR_materials_unlit` extension)
If the material type is known in advance, it should be specified with either the `metallicRoughness` or `specularGlossiness` flag. If the material type is known in advance, it should be specified with either the `metallicRoughness` or `specularGlossiness` flag.
@ -72,7 +71,7 @@ As a convenience the PBR textures may be supplied directly to the command line.
**Mapping of mtl slots to shading models** **Mapping of mtl slots to shading models**
| Slot | Metallic roughness | Specular glossiness | | Slot | Metallic roughness | Specular glossiness |
|----|-------------------|-------------------| | -------- | ------------------ | ------------------- |
| Ka | occlusion value | occlusion value | | Ka | occlusion value | occlusion value |
| Ke | emissive color | emissive color | | Ke | emissive color | emissive color |
| Kd | base color | diffuse color | | Kd | base color | diffuse color |
@ -92,7 +91,7 @@ As a convenience the PBR textures may be supplied directly to the command line.
### Command line flags: ### Command line flags:
| Flag | Description | Required | | Flag | Description | Required |
|----|-----------|--------| | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
| `-h`, `--help` | Display help. | No | | `-h`, `--help` | Display help. | No |
| `-i`, `--input` | Path to the obj file. | :white_check_mark: Yes | | `-i`, `--input` | Path to the obj file. | :white_check_mark: Yes |
| `-o`, `--output` | Path of the converted glTF or glb file. | No | | `-o`, `--output` | Path of the converted glTF or glb file. | No |
@ -119,14 +118,19 @@ As a convenience the PBR textures may be supplied directly to the command line.
## Build Instructions ## Build Instructions
Run the tests: Run the tests:
``` ```
npm run test npm run test
``` ```
To run ESLint on the entire codebase, run: To run ESLint on the entire codebase, run:
``` ```
npm run eslint npm run eslint
``` ```
To run ESLint automatically when a file is saved, run the following and leave it open in a console window: To run ESLint automatically when a file is saved, run the following and leave it open in a console window:
``` ```
npm run eslint-watch npm run eslint-watch
``` ```
@ -134,9 +138,11 @@ npm run eslint-watch
## Running Test Coverage ## Running Test Coverage
Coverage uses [nyc](https://github.com/istanbuljs/nyc). Run: Coverage uses [nyc](https://github.com/istanbuljs/nyc). Run:
``` ```
npm run coverage npm run coverage
``` ```
For complete coverage details, open `coverage/lcov-report/index.html`. For complete coverage details, open `coverage/lcov-report/index.html`.
The tests and coverage covers the Node.js module; it does not cover the command-line interface, which is tiny. The tests and coverage covers the Node.js module; it does not cover the command-line interface, which is tiny.
@ -144,6 +150,7 @@ The tests and coverage covers the Node.js module; it does not cover the command-
## Generating Documentation ## Generating Documentation
To generate the documentation: To generate the documentation:
``` ```
npm run jsdoc npm run jsdoc
``` ```
@ -157,6 +164,7 @@ Pull requests are appreciated. Please use the same [Contributor License Agreeme
--- ---
Developed by the Cesium team. Developed by the Cesium team.
<p align="center"> <p align="center">
<a href="https://cesium.com/"><img src="doc/cesium.png" onerror="this.src='cesium.png'"/></a> <a href="https://cesium.com/"><img src="doc/cesium.png" onerror="this.src='cesium.png'"/></a>
</p> </p>

View File

@ -1,10 +1,10 @@
#!/usr/bin/env node #!/usr/bin/env node
'use strict'; "use strict";
const Cesium = require('cesium'); const Cesium = require("cesium");
const fsExtra = require('fs-extra'); const fsExtra = require("fs-extra");
const path = require('path'); const path = require("path");
const yargs = require('yargs'); const yargs = require("yargs");
const obj2gltf = require('../lib/obj2gltf'); const obj2gltf = require("../lib/obj2gltf");
const defaultValue = Cesium.defaultValue; const defaultValue = Cesium.defaultValue;
const defined = Cesium.defined; const defined = Cesium.defined;
@ -14,147 +14,169 @@ const defaults = obj2gltf.defaults;
const args = process.argv; const args = process.argv;
const argv = yargs const argv = yargs
.usage('Usage: node $0 -i inputPath -o outputPath') .usage("Usage: node $0 -i inputPath -o outputPath")
.example('node $0 -i ./specs/data/box/box.obj -o box.gltf') .example("node $0 -i ./specs/data/box/box.obj -o box.gltf")
.help('h') .help("h")
.alias('h', 'help') .alias("h", "help")
.options({ .options({
input: { input: {
alias : 'i', alias: "i",
describe : 'Path to the obj file.', describe: "Path to the obj file.",
type : 'string', type: "string",
demandOption: true, demandOption: true,
coerce: function (p) { coerce: function (p) {
if (!defined(p)) { if (!defined(p)) {
return undefined; return undefined;
} }
if (p.length === 0) { if (p.length === 0) {
throw new Error('Input path must be a file name'); throw new Error("Input path must be a file name");
} }
return path.resolve(p); return path.resolve(p);
} },
}, },
output: { output: {
alias : 'o', alias: "o",
describe : 'Path of the converted glTF or glb file.', describe: "Path of the converted glTF or glb file.",
type : 'string', type: "string",
coerce: function (p) { coerce: function (p) {
if (!defined(p)) { if (!defined(p)) {
return undefined; return undefined;
} }
if (p.length === 0) { if (p.length === 0) {
throw new Error('Output path must be a file name'); throw new Error("Output path must be a file name");
} }
return path.resolve(p); return path.resolve(p);
} },
}, },
binary: { binary: {
alias : 'b', alias: "b",
describe : 'Save as binary glTF (.glb)', describe: "Save as binary glTF (.glb)",
type : 'boolean', type: "boolean",
default : defaults.binary default: defaults.binary,
}, },
separate: { separate: {
alias : 's', alias: "s",
describe : 'Write separate buffers and textures instead of embedding them in the glTF.', describe:
type : 'boolean', "Write separate buffers and textures instead of embedding them in the glTF.",
default : defaults.separate type: "boolean",
default: defaults.separate,
}, },
separateTextures: { separateTextures: {
alias : 't', alias: "t",
describe : 'Write out separate textures only.', describe: "Write out separate textures only.",
type : 'boolean', type: "boolean",
default : defaults.separateTextures default: defaults.separateTextures,
}, },
checkTransparency: { checkTransparency: {
describe : 'Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures are considered to be opaque.', describe:
type : 'boolean', "Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures are considered to be opaque.",
default : defaults.checkTransparency type: "boolean",
default: defaults.checkTransparency,
}, },
secure: { secure: {
describe : 'Prevent the converter from reading textures or mtl files outside of the input obj directory.', describe:
type : 'boolean', "Prevent the converter from reading textures or mtl files outside of the input obj directory.",
default : defaults.secure type: "boolean",
default: defaults.secure,
}, },
packOcclusion: { packOcclusion: {
describe : 'Pack the occlusion texture in the red channel of metallic-roughness texture.', describe:
type : 'boolean', "Pack the occlusion texture in the red channel of metallic-roughness texture.",
default : defaults.packOcclusion type: "boolean",
default: defaults.packOcclusion,
}, },
metallicRoughness: { metallicRoughness: {
describe : 'The values in the .mtl file are already metallic-roughness PBR values and no conversion step should be applied. Metallic is stored in the Ks and map_Ks slots and roughness is stored in the Ns and map_Ns slots.', describe:
type : 'boolean', "The values in the .mtl file are already metallic-roughness PBR values and no conversion step should be applied. Metallic is stored in the Ks and map_Ks slots and roughness is stored in the Ns and map_Ns slots.",
default : defaults.metallicRoughness type: "boolean",
default: defaults.metallicRoughness,
}, },
specularGlossiness: { specularGlossiness: {
describe : 'The values in the .mtl file are already specular-glossiness PBR values and no conversion step should be applied. Specular is stored in the Ks and map_Ks slots and glossiness is stored in the Ns and map_Ns slots. The glTF will be saved with the KHR_materials_pbrSpecularGlossiness extension.', describe:
type : 'boolean', "The values in the .mtl file are already specular-glossiness PBR values and no conversion step should be applied. Specular is stored in the Ks and map_Ks slots and glossiness is stored in the Ns and map_Ns slots. The glTF will be saved with the KHR_materials_pbrSpecularGlossiness extension.",
default : defaults.specularGlossiness type: "boolean",
default: defaults.specularGlossiness,
}, },
unlit: { unlit: {
describe : 'The glTF will be saved with the KHR_materials_unlit extension.', describe:
type : 'boolean', "The glTF will be saved with the KHR_materials_unlit extension.",
default : defaults.unlit type: "boolean",
default: defaults.unlit,
}, },
metallicRoughnessOcclusionTexture: { metallicRoughnessOcclusionTexture: {
describe : 'Path to the metallic-roughness-occlusion texture that should override textures in the .mtl file, where occlusion is stored in the red channel, roughness is stored in the green channel, and metallic is stored in the blue channel. The model will be saved with a pbrMetallicRoughness material. This is often convenient in workflows where the .mtl does not exist or is not set up to use PBR materials. Intended for models with a single material', describe:
type : 'string', "Path to the metallic-roughness-occlusion texture that should override textures in the .mtl file, where occlusion is stored in the red channel, roughness is stored in the green channel, and metallic is stored in the blue channel. The model will be saved with a pbrMetallicRoughness material. This is often convenient in workflows where the .mtl does not exist or is not set up to use PBR materials. Intended for models with a single material",
normalize : true type: "string",
normalize: true,
}, },
specularGlossinessTexture: { specularGlossinessTexture: {
describe : 'Path to the specular-glossiness texture that should override textures in the .mtl file, where specular color is stored in the red, green, and blue channels and specular glossiness is stored in the alpha channel. The model will be saved with a material using the KHR_materials_pbrSpecularGlossiness extension.', describe:
type : 'string', "Path to the specular-glossiness texture that should override textures in the .mtl file, where specular color is stored in the red, green, and blue channels and specular glossiness is stored in the alpha channel. The model will be saved with a material using the KHR_materials_pbrSpecularGlossiness extension.",
normalize : true type: "string",
normalize: true,
}, },
occlusionTexture: { occlusionTexture: {
describe : 'Path to the occlusion texture that should override textures in the .mtl file.', describe:
type : 'string', "Path to the occlusion texture that should override textures in the .mtl file.",
normalize : true type: "string",
normalize: true,
}, },
normalTexture: { normalTexture: {
describe : 'Path to the normal texture that should override textures in the .mtl file.', describe:
type : 'string', "Path to the normal texture that should override textures in the .mtl file.",
normalize : true type: "string",
normalize: true,
}, },
baseColorTexture: { baseColorTexture: {
describe : 'Path to the baseColor/diffuse texture that should override textures in the .mtl file.', describe:
type : 'string', "Path to the baseColor/diffuse texture that should override textures in the .mtl file.",
normalize : true type: "string",
normalize: true,
}, },
emissiveTexture: { emissiveTexture: {
describe : 'Path to the emissive texture that should override textures in the .mtl file.', describe:
type : 'string', "Path to the emissive texture that should override textures in the .mtl file.",
normalize : true type: "string",
normalize: true,
}, },
alphaTexture: { alphaTexture: {
describe : 'Path to the alpha texture that should override textures in the .mtl file.' describe:
"Path to the alpha texture that should override textures in the .mtl file.",
}, },
inputUpAxis: { inputUpAxis: {
describe: 'Up axis of the obj.', describe: "Up axis of the obj.",
choices: ['X', 'Y', 'Z'], choices: ["X", "Y", "Z"],
type: 'string', type: "string",
default: 'Y' default: "Y",
}, },
outputUpAxis: { outputUpAxis: {
describe: 'Up axis of the converted glTF.', describe: "Up axis of the converted glTF.",
choices: ['X', 'Y', 'Z'], choices: ["X", "Y", "Z"],
type: 'string', type: "string",
default: 'Y' default: "Y",
}, },
triangleWindingOrderSanitization: { triangleWindingOrderSanitization: {
describe: 'Apply triangle winding order sanitization.', describe: "Apply triangle winding order sanitization.",
type: 'boolean', type: "boolean",
default: defaults.triangleWindingOrderSanitization default: defaults.triangleWindingOrderSanitization,
} },
}).parse(args); })
.parse(args);
if (argv.metallicRoughness + argv.specularGlossiness > 1) { if (argv.metallicRoughness + argv.specularGlossiness > 1) {
console.error('Only one material type may be set from [--metallicRoughness, --specularGlossiness].'); console.error(
"Only one material type may be set from [--metallicRoughness, --specularGlossiness]."
);
process.exit(1); process.exit(1);
} }
if (defined(argv.metallicRoughnessOcclusionTexture) && defined(argv.specularGlossinessTexture)) { if (
console.error('--metallicRoughnessOcclusionTexture and --specularGlossinessTexture cannot both be set.'); defined(argv.metallicRoughnessOcclusionTexture) &&
defined(argv.specularGlossinessTexture)
) {
console.error(
"--metallicRoughnessOcclusionTexture and --specularGlossinessTexture cannot both be set."
);
process.exit(1); process.exit(1);
} }
@ -164,8 +186,8 @@ let gltfPath = argv.output;
const filename = defaultValue(gltfPath, objPath); const filename = defaultValue(gltfPath, objPath);
const name = path.basename(filename, path.extname(filename)); const name = path.basename(filename, path.extname(filename));
const outputDirectory = path.dirname(filename); const outputDirectory = path.dirname(filename);
const binary = argv.binary || path.extname(filename).toLowerCase() === '.glb'; const binary = argv.binary || path.extname(filename).toLowerCase() === ".glb";
const extension = binary ? '.glb' : '.gltf'; const extension = binary ? ".glb" : ".gltf";
gltfPath = path.join(outputDirectory, name + extension); gltfPath = path.join(outputDirectory, name + extension);
@ -176,7 +198,7 @@ const overridingTextures = {
normalTexture: argv.normalTexture, normalTexture: argv.normalTexture,
baseColorTexture: argv.baseColorTexture, baseColorTexture: argv.baseColorTexture,
emissiveTexture: argv.emissiveTexture, emissiveTexture: argv.emissiveTexture,
alphaTexture : argv.alphaTexture alphaTexture: argv.alphaTexture,
}; };
const options = { const options = {
@ -193,10 +215,10 @@ const options = {
outputDirectory: outputDirectory, outputDirectory: outputDirectory,
inputUpAxis: argv.inputUpAxis, inputUpAxis: argv.inputUpAxis,
outputUpAxis: argv.outputUpAxis, outputUpAxis: argv.outputUpAxis,
triangleWindingOrderSanitization: argv.triangleWindingOrderSanitization triangleWindingOrderSanitization: argv.triangleWindingOrderSanitization,
}; };
console.time('Total'); console.time("Total");
obj2gltf(objPath, options) obj2gltf(objPath, options)
.then(function (gltf) { .then(function (gltf) {
@ -205,12 +227,12 @@ obj2gltf(objPath, options)
return fsExtra.outputFile(gltfPath, gltf); return fsExtra.outputFile(gltfPath, gltf);
} }
const jsonOptions = { const jsonOptions = {
spaces : 2 spaces: 2,
}; };
return fsExtra.outputJson(gltfPath, gltf, jsonOptions); return fsExtra.outputJson(gltfPath, gltf, jsonOptions);
}) })
.then(function () { .then(function () {
console.timeEnd('Total'); console.timeEnd("Total");
}) })
.catch(function (error) { .catch(function (error) {
console.log(error.message); console.log(error.message);

View File

@ -1,40 +1,49 @@
'use strict'; "use strict";
const Cesium = require('cesium'); const Cesium = require("cesium");
const Promise = require('bluebird'); const Promise = require("bluebird");
const child_process = require('child_process'); const child_process = require("child_process");
const fsExtra = require('fs-extra'); const fsExtra = require("fs-extra");
const gulp = require('gulp'); const gulp = require("gulp");
const Jasmine = require('jasmine'); const Jasmine = require("jasmine");
const JasmineSpecReporter = require('jasmine-spec-reporter').SpecReporter; const JasmineSpecReporter = require("jasmine-spec-reporter").SpecReporter;
const open = require('open'); const open = require("open");
const path = require('path'); const path = require("path");
const yargs = require('yargs'); const yargs = require("yargs");
const defined = Cesium.defined; const defined = Cesium.defined;
const argv = yargs.argv; const argv = yargs.argv;
// Add third-party node module binaries to the system path // Add third-party node module binaries to the system path
// since some tasks need to call them directly. // since some tasks need to call them directly.
const environmentSeparator = process.platform === 'win32' ? ';' : ':'; const environmentSeparator = process.platform === "win32" ? ";" : ":";
const nodeBinaries = path.join(__dirname, 'node_modules', '.bin'); const nodeBinaries = path.join(__dirname, "node_modules", ".bin");
process.env.PATH += environmentSeparator + nodeBinaries; process.env.PATH += environmentSeparator + nodeBinaries;
const specFiles = ['**/*.js', '!node_modules/**', '!coverage/**', '!doc/**', '!bin/**']; const specFiles = [
"**/*.js",
"!node_modules/**",
"!coverage/**",
"!doc/**",
"!bin/**",
];
module.exports = { module.exports = {
test: test, test: test,
'test-watch': testWatch, "test-watch": testWatch,
coverage: coverage, coverage: coverage,
cloc: cloc cloc: cloc,
}; };
function test(done) { function test(done) {
const jasmine = new Jasmine(); const jasmine = new Jasmine();
jasmine.loadConfigFile('specs/jasmine.json'); jasmine.loadConfigFile("specs/jasmine.json");
jasmine.addReporter(new JasmineSpecReporter({ jasmine.addReporter(
displaySuccessfulSpec: !defined(argv.suppressPassed) || !argv.suppressPassed new JasmineSpecReporter({
})); displaySuccessfulSpec:
!defined(argv.suppressPassed) || !argv.suppressPassed,
})
);
jasmine.execute(); jasmine.execute();
jasmine.onComplete(function (passed) { jasmine.onComplete(function (passed) {
done(argv.failTaskOnError && !passed ? 1 : 0); done(argv.failTaskOnError && !passed ? 1 : 0);
@ -42,48 +51,50 @@ function test(done) {
} }
function testWatch() { function testWatch() {
return gulp.watch(specFiles).on('change', function () { return gulp.watch(specFiles).on("change", function () {
// We can't simply depend on the test task because Jasmine // We can't simply depend on the test task because Jasmine
// does not like being run multiple times in the same process. // does not like being run multiple times in the same process.
try { try {
child_process.execSync('jasmine JASMINE_CONFIG_PATH=specs/jasmine.json', { child_process.execSync("jasmine JASMINE_CONFIG_PATH=specs/jasmine.json", {
stdio: [process.stdin, process.stdout, process.stderr] stdio: [process.stdin, process.stdout, process.stderr],
}); });
} catch (exception) { } catch (exception) {
console.log('Tests failed to execute.'); console.log("Tests failed to execute.");
} }
}); });
} }
async function coverage() { async function coverage() {
fsExtra.removeSync('coverage/server'); fsExtra.removeSync("coverage/server");
child_process.execSync('nyc' + child_process.execSync(
' --all' + "nyc" +
' --reporter=lcov' + " --all" +
' --dir coverage' + " --reporter=lcov" +
" --dir coverage" +
' -x "specs/**" -x "coverage/**" -x "doc/**" -x "bin/**" -x "index.js" -x "gulpfile.js"' + ' -x "specs/**" -x "coverage/**" -x "doc/**" -x "bin/**" -x "index.js" -x "gulpfile.js"' +
' node_modules/jasmine/bin/jasmine.js' + " node_modules/jasmine/bin/jasmine.js" +
' JASMINE_CONFIG_PATH=specs/jasmine.json', { " JASMINE_CONFIG_PATH=specs/jasmine.json",
stdio: [process.stdin, process.stdout, process.stderr] {
}); stdio: [process.stdin, process.stdout, process.stderr],
open('coverage/lcov-report/index.html'); }
);
open("coverage/lcov-report/index.html");
} }
function cloc() { function cloc() {
let cmdLine; let cmdLine;
const clocPath = path.join('node_modules', 'cloc', 'lib', 'cloc'); const clocPath = path.join("node_modules", "cloc", "lib", "cloc");
//Run cloc on primary Source files only //Run cloc on primary Source files only
const source = new Promise(function (resolve, reject) { const source = new Promise(function (resolve, reject) {
cmdLine = 'perl ' + clocPath + ' --quiet --progress-rate=0' + cmdLine = "perl " + clocPath + " --quiet --progress-rate=0" + " lib/ bin/";
' lib/ bin/';
child_process.exec(cmdLine, function (error, stdout, stderr) { child_process.exec(cmdLine, function (error, stdout, stderr) {
if (error) { if (error) {
console.log(stderr); console.log(stderr);
return reject(error); return reject(error);
} }
console.log('Source:'); console.log("Source:");
console.log(stdout); console.log(stdout);
resolve(); resolve();
}); });
@ -92,14 +103,14 @@ function cloc() {
//If running cloc on source succeeded, also run it on the tests. //If running cloc on source succeeded, also run it on the tests.
return source.then(function () { return source.then(function () {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
cmdLine = 'perl ' + clocPath + ' --quiet --progress-rate=0' + cmdLine =
' specs/lib/'; "perl " + clocPath + " --quiet --progress-rate=0" + " specs/lib/";
child_process.exec(cmdLine, function (error, stdout, stderr) { child_process.exec(cmdLine, function (error, stdout, stderr) {
if (error) { if (error) {
console.log(stderr); console.log(stderr);
return reject(error); return reject(error);
} }
console.log('Specs:'); console.log("Specs:");
console.log(stdout); console.log(stdout);
resolve(); resolve();
}); });

View File

@ -1,2 +1,2 @@
'use strict'; "use strict";
module.exports = require('./lib/obj2gltf'); module.exports = require("./lib/obj2gltf");

View File

@ -1,5 +1,5 @@
'use strict'; "use strict";
const Cesium = require('cesium'); const Cesium = require("cesium");
const ComponentDatatype = Cesium.ComponentDatatype; const ComponentDatatype = Cesium.ComponentDatatype;
@ -24,7 +24,10 @@ function ArrayStorage(componentDatatype) {
} }
function resize(storage, length) { function resize(storage, length) {
const typedArray = ComponentDatatype.createTypedArray(storage.componentDatatype, length); const typedArray = ComponentDatatype.createTypedArray(
storage.componentDatatype,
length
);
typedArray.set(storage.typedArray); typedArray.set(storage.typedArray);
storage.typedArray = typedArray; storage.typedArray = typedArray;
} }
@ -57,7 +60,7 @@ const sizeOfFloat = 4;
ArrayStorage.prototype.toUint16Buffer = function () { ArrayStorage.prototype.toUint16Buffer = function () {
const length = this.length; const length = this.length;
const typedArray = this.typedArray; const typedArray = this.typedArray;
const paddedLength = length + ((length % 2 === 0) ? 0 : 1); // Round to next multiple of 2 const paddedLength = length + (length % 2 === 0 ? 0 : 1); // Round to next multiple of 2
const buffer = Buffer.alloc(paddedLength * sizeOfUint16); const buffer = Buffer.alloc(paddedLength * sizeOfUint16);
for (let i = 0; i < length; ++i) { for (let i = 0; i < length; ++i) {
buffer.writeUInt16LE(typedArray[i], i * sizeOfUint16); buffer.writeUInt16LE(typedArray[i], i * sizeOfUint16);
@ -101,6 +104,6 @@ ArrayStorage.prototype.getMinMax = function(components) {
} }
return { return {
min: min, min: min,
max : max max: max,
}; };
}; };

View File

@ -1,4 +1,4 @@
'use strict'; "use strict";
module.exports = Texture; module.exports = Texture;

View File

@ -1,9 +1,9 @@
'use strict'; "use strict";
const BUFFER_MAX_BYTE_LENGTH = require('buffer').constants.MAX_LENGTH; const BUFFER_MAX_BYTE_LENGTH = require("buffer").constants.MAX_LENGTH;
const Cesium = require('cesium'); const Cesium = require("cesium");
const getBufferPadded = require('./getBufferPadded'); const getBufferPadded = require("./getBufferPadded");
const getDefaultMaterial = require('./loadMtl').getDefaultMaterial; const getDefaultMaterial = require("./loadMtl").getDefaultMaterial;
const Texture = require('./Texture'); const Texture = require("./Texture");
const defaultValue = Cesium.defaultValue; const defaultValue = Cesium.defaultValue;
const defined = Cesium.defined; const defined = Cesium.defined;
@ -42,16 +42,16 @@ function createGltf(objData, options) {
samplers: [], samplers: [],
scene: 0, scene: 0,
scenes: [], scenes: [],
textures : [] textures: [],
}; };
gltf.asset = { gltf.asset = {
generator : 'obj2gltf', generator: "obj2gltf",
version: '2.0' version: "2.0",
}; };
gltf.scenes.push({ gltf.scenes.push({
nodes : [] nodes: [],
}); });
const bufferState = { const bufferState = {
@ -62,7 +62,7 @@ function createGltf(objData, options) {
positionAccessors: [], positionAccessors: [],
normalAccessors: [], normalAccessors: [],
uvAccessors: [], uvAccessors: [],
indexAccessors : [] indexAccessors: [],
}; };
const uint32Indices = requiresUint32Indices(nodes); const uint32Indices = requiresUint32Indices(nodes);
@ -74,14 +74,28 @@ function createGltf(objData, options) {
const meshesLength = meshes.length; const meshesLength = meshes.length;
if (meshesLength === 1) { if (meshesLength === 1) {
const meshIndex = addMesh(gltf, materials, bufferState, uint32Indices, meshes[0], options); const meshIndex = addMesh(
gltf,
materials,
bufferState,
uint32Indices,
meshes[0],
options
);
addNode(gltf, node.name, meshIndex, undefined); addNode(gltf, node.name, meshIndex, undefined);
} else { } else {
// Add meshes as child nodes // Add meshes as child nodes
const parentIndex = addNode(gltf, node.name); const parentIndex = addNode(gltf, node.name);
for (let j = 0; j < meshesLength; ++j) { for (let j = 0; j < meshesLength; ++j) {
const mesh = meshes[j]; const mesh = meshes[j];
const meshIndex = addMesh(gltf, materials, bufferState, uint32Indices, mesh, options); const meshIndex = addMesh(
gltf,
materials,
bufferState,
uint32Indices,
mesh,
options
);
addNode(gltf, mesh.name, meshIndex, parentIndex); addNode(gltf, mesh.name, meshIndex, parentIndex);
} }
} }
@ -90,20 +104,20 @@ function createGltf(objData, options) {
if (gltf.images.length > 0) { if (gltf.images.length > 0) {
gltf.samplers.push({ gltf.samplers.push({
wrapS: WebGLConstants.REPEAT, wrapS: WebGLConstants.REPEAT,
wrapT : WebGLConstants.REPEAT wrapT: WebGLConstants.REPEAT,
}); });
} }
addBuffers(gltf, bufferState, name, options.separate); addBuffers(gltf, bufferState, name, options.separate);
if (options.specularGlossiness) { if (options.specularGlossiness) {
gltf.extensionsUsed.push('KHR_materials_pbrSpecularGlossiness'); gltf.extensionsUsed.push("KHR_materials_pbrSpecularGlossiness");
gltf.extensionsRequired.push('KHR_materials_pbrSpecularGlossiness'); gltf.extensionsRequired.push("KHR_materials_pbrSpecularGlossiness");
} }
if (options.unlit) { if (options.unlit) {
gltf.extensionsUsed.push('KHR_materials_unlit'); gltf.extensionsUsed.push("KHR_materials_unlit");
gltf.extensionsRequired.push('KHR_materials_unlit'); gltf.extensionsRequired.push("KHR_materials_unlit");
} }
return gltf; return gltf;
@ -116,7 +130,9 @@ function addCombinedBufferView(gltf, buffers, accessors, byteStride, target) {
} }
const bufferViewIndex = gltf.bufferViews.length; const bufferViewIndex = gltf.bufferViews.length;
const previousBufferView = gltf.bufferViews[bufferViewIndex - 1]; const previousBufferView = gltf.bufferViews[bufferViewIndex - 1];
const byteOffset = defined(previousBufferView) ? previousBufferView.byteOffset + previousBufferView.byteLength : 0; const byteOffset = defined(previousBufferView)
? previousBufferView.byteOffset + previousBufferView.byteLength
: 0;
let byteLength = 0; let byteLength = 0;
for (let i = 0; i < length; ++i) { for (let i = 0; i < length; ++i) {
const accessor = gltf.accessors[accessors[i]]; const accessor = gltf.accessors[accessors[i]];
@ -125,23 +141,52 @@ function addCombinedBufferView(gltf, buffers, accessors, byteStride, target) {
byteLength += buffers[i].length; byteLength += buffers[i].length;
} }
gltf.bufferViews.push({ gltf.bufferViews.push({
name : 'bufferView_' + bufferViewIndex, name: "bufferView_" + bufferViewIndex,
buffer: 0, buffer: 0,
byteLength: byteLength, byteLength: byteLength,
byteOffset: byteOffset, byteOffset: byteOffset,
byteStride: byteStride, byteStride: byteStride,
target : target target: target,
}); });
} }
function addCombinedBuffers(gltf, bufferState, name) { function addCombinedBuffers(gltf, bufferState, name) {
addCombinedBufferView(gltf, bufferState.positionBuffers, bufferState.positionAccessors, 12, WebGLConstants.ARRAY_BUFFER); addCombinedBufferView(
addCombinedBufferView(gltf, bufferState.normalBuffers, bufferState.normalAccessors, 12, WebGLConstants.ARRAY_BUFFER); gltf,
addCombinedBufferView(gltf, bufferState.uvBuffers, bufferState.uvAccessors, 8, WebGLConstants.ARRAY_BUFFER); bufferState.positionBuffers,
addCombinedBufferView(gltf, bufferState.indexBuffers, bufferState.indexAccessors, undefined, WebGLConstants.ELEMENT_ARRAY_BUFFER); bufferState.positionAccessors,
12,
WebGLConstants.ARRAY_BUFFER
);
addCombinedBufferView(
gltf,
bufferState.normalBuffers,
bufferState.normalAccessors,
12,
WebGLConstants.ARRAY_BUFFER
);
addCombinedBufferView(
gltf,
bufferState.uvBuffers,
bufferState.uvAccessors,
8,
WebGLConstants.ARRAY_BUFFER
);
addCombinedBufferView(
gltf,
bufferState.indexBuffers,
bufferState.indexAccessors,
undefined,
WebGLConstants.ELEMENT_ARRAY_BUFFER
);
let buffers = []; let buffers = [];
buffers = buffers.concat(bufferState.positionBuffers, bufferState.normalBuffers, bufferState.uvBuffers, bufferState.indexBuffers); buffers = buffers.concat(
bufferState.positionBuffers,
bufferState.normalBuffers,
bufferState.uvBuffers,
bufferState.indexBuffers
);
const buffer = getBufferPadded(Buffer.concat(buffers)); const buffer = getBufferPadded(Buffer.concat(buffers));
gltf.buffers.push({ gltf.buffers.push({
@ -149,24 +194,31 @@ function addCombinedBuffers(gltf, bufferState, name) {
byteLength: buffer.length, byteLength: buffer.length,
extras: { extras: {
_obj2gltf: { _obj2gltf: {
source : buffer source: buffer,
} },
} },
}); });
} }
function addSeparateBufferView(gltf, buffer, accessor, byteStride, target, name) { function addSeparateBufferView(
gltf,
buffer,
accessor,
byteStride,
target,
name
) {
const bufferIndex = gltf.buffers.length; const bufferIndex = gltf.buffers.length;
const bufferViewIndex = gltf.bufferViews.length; const bufferViewIndex = gltf.bufferViews.length;
gltf.buffers.push({ gltf.buffers.push({
name : name + '_' + bufferIndex, name: name + "_" + bufferIndex,
byteLength: buffer.length, byteLength: buffer.length,
extras: { extras: {
_obj2gltf: { _obj2gltf: {
source : buffer source: buffer,
} },
} },
}); });
gltf.bufferViews.push({ gltf.bufferViews.push({
@ -174,36 +226,82 @@ function addSeparateBufferView(gltf, buffer, accessor, byteStride, target, name)
byteLength: buffer.length, byteLength: buffer.length,
byteOffset: 0, byteOffset: 0,
byteStride: byteStride, byteStride: byteStride,
target : target target: target,
}); });
gltf.accessors[accessor].bufferView = bufferViewIndex; gltf.accessors[accessor].bufferView = bufferViewIndex;
gltf.accessors[accessor].byteOffset = 0; gltf.accessors[accessor].byteOffset = 0;
} }
function addSeparateBufferViews(gltf, buffers, accessors, byteStride, target, name) { function addSeparateBufferViews(
gltf,
buffers,
accessors,
byteStride,
target,
name
) {
const length = buffers.length; const length = buffers.length;
for (let i = 0; i < length; ++i) { for (let i = 0; i < length; ++i) {
addSeparateBufferView(gltf, buffers[i], accessors[i], byteStride, target, name); addSeparateBufferView(
gltf,
buffers[i],
accessors[i],
byteStride,
target,
name
);
} }
} }
function addSeparateBuffers(gltf, bufferState, name) { function addSeparateBuffers(gltf, bufferState, name) {
addSeparateBufferViews(gltf, bufferState.positionBuffers, bufferState.positionAccessors, 12, WebGLConstants.ARRAY_BUFFER, name); addSeparateBufferViews(
addSeparateBufferViews(gltf, bufferState.normalBuffers, bufferState.normalAccessors, 12, WebGLConstants.ARRAY_BUFFER, name); gltf,
addSeparateBufferViews(gltf, bufferState.uvBuffers, bufferState.uvAccessors, 8, WebGLConstants.ARRAY_BUFFER, name); bufferState.positionBuffers,
addSeparateBufferViews(gltf, bufferState.indexBuffers, bufferState.indexAccessors, undefined, WebGLConstants.ELEMENT_ARRAY_BUFFER, name); bufferState.positionAccessors,
12,
WebGLConstants.ARRAY_BUFFER,
name
);
addSeparateBufferViews(
gltf,
bufferState.normalBuffers,
bufferState.normalAccessors,
12,
WebGLConstants.ARRAY_BUFFER,
name
);
addSeparateBufferViews(
gltf,
bufferState.uvBuffers,
bufferState.uvAccessors,
8,
WebGLConstants.ARRAY_BUFFER,
name
);
addSeparateBufferViews(
gltf,
bufferState.indexBuffers,
bufferState.indexAccessors,
undefined,
WebGLConstants.ELEMENT_ARRAY_BUFFER,
name
);
} }
function addBuffers(gltf, bufferState, name, separate) { function addBuffers(gltf, bufferState, name, separate) {
const buffers = bufferState.positionBuffers.concat(bufferState.normalBuffers, bufferState.uvBuffers, bufferState.indexBuffers); const buffers = bufferState.positionBuffers.concat(
bufferState.normalBuffers,
bufferState.uvBuffers,
bufferState.indexBuffers
);
const buffersLength = buffers.length; const buffersLength = buffers.length;
let buffersByteLength = 0; let buffersByteLength = 0;
for (let i = 0; i < buffersLength; ++i) { for (let i = 0; i < buffersLength; ++i) {
buffersByteLength += buffers[i].length; buffersByteLength += buffers[i].length;
} }
if (separate && (buffersByteLength > createGltf._getBufferMaxByteLength())) { if (separate && buffersByteLength > createGltf._getBufferMaxByteLength()) {
// Don't combine buffers if the combined buffer will exceed the Node limit. // Don't combine buffers if the combined buffer will exceed the Node limit.
addSeparateBuffers(gltf, bufferState, name); addSeparateBuffers(gltf, bufferState, name);
} else { } else {
@ -220,14 +318,14 @@ function addTexture(gltf, texture) {
gltf.images.push({ gltf.images.push({
name: imageName, name: imageName,
extras: { extras: {
_obj2gltf : texture _obj2gltf: texture,
} },
}); });
gltf.textures.push({ gltf.textures.push({
name: textureName, name: textureName,
sampler: 0, sampler: 0,
source : imageIndex source: imageIndex,
}); });
return textureIndex; return textureIndex;
@ -249,12 +347,12 @@ function getTexture(gltf, texture) {
} }
return { return {
index : textureIndex index: textureIndex,
}; };
} }
function cloneMaterial(material, removeTextures) { function cloneMaterial(material, removeTextures) {
if (typeof material !== 'object') { if (typeof material !== "object") {
return material; return material;
} else if (material instanceof Texture) { } else if (material instanceof Texture) {
if (removeTextures) { if (removeTextures) {
@ -284,7 +382,7 @@ function resolveTextures(gltf, material) {
const property = material[name]; const property = material[name];
if (property instanceof Texture) { if (property instanceof Texture) {
material[name] = getTexture(gltf, property); material[name] = getTexture(gltf, property);
} else if (!Array.isArray(property) && (typeof property === 'object')) { } else if (!Array.isArray(property) && typeof property === "object") {
resolveTextures(gltf, property); resolveTextures(gltf, property);
} }
} }
@ -334,18 +432,26 @@ function getOrCreateGltfMaterial(gltf, materials, materialName, options) {
} }
function primitiveInfoMatch(a, b) { function primitiveInfoMatch(a, b) {
return a.hasUvs === b.hasUvs && return a.hasUvs === b.hasUvs && a.hasNormals === b.hasNormals;
a.hasNormals === b.hasNormals;
} }
function getSplitMaterialName(originalMaterialName, primitiveInfo, primitiveInfoByMaterial) { function getSplitMaterialName(
originalMaterialName,
primitiveInfo,
primitiveInfoByMaterial
) {
let splitMaterialName = originalMaterialName; let splitMaterialName = originalMaterialName;
let suffix = 2; let suffix = 2;
while (defined(primitiveInfoByMaterial[splitMaterialName])) { while (defined(primitiveInfoByMaterial[splitMaterialName])) {
if (primitiveInfoMatch(primitiveInfo, primitiveInfoByMaterial[splitMaterialName])) { if (
primitiveInfoMatch(
primitiveInfo,
primitiveInfoByMaterial[splitMaterialName]
)
) {
break; break;
} }
splitMaterialName = originalMaterialName + '-' + suffix++; splitMaterialName = originalMaterialName + "-" + suffix++;
} }
return splitMaterialName; return splitMaterialName;
} }
@ -366,19 +472,32 @@ function splitIncompatibleMaterials(nodes, materials, options) {
const hasNormals = primitive.normals.length > 0; const hasNormals = primitive.normals.length > 0;
const primitiveInfo = { const primitiveInfo = {
hasUvs: hasUvs, hasUvs: hasUvs,
hasNormals : hasNormals hasNormals: hasNormals,
}; };
const originalMaterialName = defaultValue(primitive.material, 'default'); const originalMaterialName = defaultValue(
const splitMaterialName = getSplitMaterialName(originalMaterialName, primitiveInfo, primitiveInfoByMaterial); primitive.material,
"default"
);
const splitMaterialName = getSplitMaterialName(
originalMaterialName,
primitiveInfo,
primitiveInfoByMaterial
);
primitive.material = splitMaterialName; primitive.material = splitMaterialName;
primitiveInfoByMaterial[splitMaterialName] = primitiveInfo; primitiveInfoByMaterial[splitMaterialName] = primitiveInfo;
let splitMaterial = getMaterialByName(splitMaterials, splitMaterialName); let splitMaterial = getMaterialByName(
splitMaterials,
splitMaterialName
);
if (defined(splitMaterial)) { if (defined(splitMaterial)) {
continue; continue;
} }
const originalMaterial = getMaterialByName(materials, originalMaterialName); const originalMaterial = getMaterialByName(
materials,
originalMaterialName
);
if (defined(originalMaterial)) { if (defined(originalMaterial)) {
splitMaterial = cloneMaterial(originalMaterial, !hasUvs); splitMaterial = cloneMaterial(originalMaterial, !hasUvs);
} else { } else {
@ -395,7 +514,7 @@ function splitIncompatibleMaterials(nodes, materials, options) {
function addVertexAttribute(gltf, array, components, name) { function addVertexAttribute(gltf, array, components, name) {
const count = array.length / components; const count = array.length / components;
const minMax = array.getMinMax(components); const minMax = array.getMinMax(components);
const type = (components === 3 ? 'VEC3' : 'VEC2'); const type = components === 3 ? "VEC3" : "VEC2";
const accessor = { const accessor = {
name: name, name: name,
@ -403,7 +522,7 @@ function addVertexAttribute(gltf, array, components, name) {
count: count, count: count,
min: minMax.min, min: minMax.min,
max: minMax.max, max: minMax.max,
type : type type: type,
}; };
const accessorIndex = gltf.accessors.length; const accessorIndex = gltf.accessors.length;
@ -412,7 +531,9 @@ function addVertexAttribute(gltf, array, components, name) {
} }
function addIndexArray(gltf, array, uint32Indices, name) { function addIndexArray(gltf, array, uint32Indices, name) {
const componentType = uint32Indices ? WebGLConstants.UNSIGNED_INT : WebGLConstants.UNSIGNED_SHORT; const componentType = uint32Indices
? WebGLConstants.UNSIGNED_INT
: WebGLConstants.UNSIGNED_SHORT;
const count = array.length; const count = array.length;
const minMax = array.getMinMax(1); const minMax = array.getMinMax(1);
@ -422,7 +543,7 @@ function addIndexArray(gltf, array, uint32Indices, name) {
count: count, count: count,
min: minMax.min, min: minMax.min,
max: minMax.max, max: minMax.max,
type : 'SCALAR' type: "SCALAR",
}; };
const accessorIndex = gltf.accessors.length; const accessorIndex = gltf.accessors.length;
@ -450,33 +571,64 @@ function requiresUint32Indices(nodes) {
return false; return false;
} }
function addPrimitive(gltf, materials, bufferState, uint32Indices, mesh, primitive, index, options) { function addPrimitive(
gltf,
materials,
bufferState,
uint32Indices,
mesh,
primitive,
index,
options
) {
const hasPositions = primitive.positions.length > 0; const hasPositions = primitive.positions.length > 0;
const hasNormals = primitive.normals.length > 0; const hasNormals = primitive.normals.length > 0;
const hasUVs = primitive.uvs.length > 0; const hasUVs = primitive.uvs.length > 0;
const attributes = {}; const attributes = {};
if (hasPositions) { if (hasPositions) {
const accessorIndex = addVertexAttribute(gltf, primitive.positions, 3, mesh.name + '_' + index + '_positions'); const accessorIndex = addVertexAttribute(
gltf,
primitive.positions,
3,
mesh.name + "_" + index + "_positions"
);
attributes.POSITION = accessorIndex; attributes.POSITION = accessorIndex;
bufferState.positionBuffers.push(primitive.positions.toFloatBuffer()); bufferState.positionBuffers.push(primitive.positions.toFloatBuffer());
bufferState.positionAccessors.push(accessorIndex); bufferState.positionAccessors.push(accessorIndex);
} }
if (hasNormals) { if (hasNormals) {
const accessorIndex = addVertexAttribute(gltf, primitive.normals, 3, mesh.name + '_' + index + '_normals'); const accessorIndex = addVertexAttribute(
gltf,
primitive.normals,
3,
mesh.name + "_" + index + "_normals"
);
attributes.NORMAL = accessorIndex; attributes.NORMAL = accessorIndex;
bufferState.normalBuffers.push(primitive.normals.toFloatBuffer()); bufferState.normalBuffers.push(primitive.normals.toFloatBuffer());
bufferState.normalAccessors.push(accessorIndex); bufferState.normalAccessors.push(accessorIndex);
} }
if (hasUVs) { if (hasUVs) {
const accessorIndex = addVertexAttribute(gltf, primitive.uvs, 2, mesh.name + '_' + index + '_texcoords'); const accessorIndex = addVertexAttribute(
gltf,
primitive.uvs,
2,
mesh.name + "_" + index + "_texcoords"
);
attributes.TEXCOORD_0 = accessorIndex; attributes.TEXCOORD_0 = accessorIndex;
bufferState.uvBuffers.push(primitive.uvs.toFloatBuffer()); bufferState.uvBuffers.push(primitive.uvs.toFloatBuffer());
bufferState.uvAccessors.push(accessorIndex); bufferState.uvAccessors.push(accessorIndex);
} }
const indexAccessorIndex = addIndexArray(gltf, primitive.indices, uint32Indices, mesh.name + '_' + index + '_indices'); const indexAccessorIndex = addIndexArray(
const indexBuffer = uint32Indices ? primitive.indices.toUint32Buffer() : primitive.indices.toUint16Buffer(); gltf,
primitive.indices,
uint32Indices,
mesh.name + "_" + index + "_indices"
);
const indexBuffer = uint32Indices
? primitive.indices.toUint32Buffer()
: primitive.indices.toUint16Buffer();
bufferState.indexBuffers.push(indexBuffer); bufferState.indexBuffers.push(indexBuffer);
bufferState.indexAccessors.push(indexAccessorIndex); bufferState.indexAccessors.push(indexAccessorIndex);
@ -486,13 +638,18 @@ function addPrimitive(gltf, materials, bufferState, uint32Indices, mesh, primiti
primitive.uvs = undefined; primitive.uvs = undefined;
primitive.indices = undefined; primitive.indices = undefined;
const materialIndex = getOrCreateGltfMaterial(gltf, materials, primitive.material, options); const materialIndex = getOrCreateGltfMaterial(
gltf,
materials,
primitive.material,
options
);
return { return {
attributes: attributes, attributes: attributes,
indices: indexAccessorIndex, indices: indexAccessorIndex,
material: materialIndex, material: materialIndex,
mode : WebGLConstants.TRIANGLES mode: WebGLConstants.TRIANGLES,
}; };
} }
@ -501,12 +658,23 @@ function addMesh(gltf, materials, bufferState, uint32Indices, mesh, options) {
const primitives = mesh.primitives; const primitives = mesh.primitives;
const primitivesLength = primitives.length; const primitivesLength = primitives.length;
for (let i = 0; i < primitivesLength; ++i) { for (let i = 0; i < primitivesLength; ++i) {
gltfPrimitives.push(addPrimitive(gltf, materials, bufferState, uint32Indices, mesh, primitives[i], i, options)); gltfPrimitives.push(
addPrimitive(
gltf,
materials,
bufferState,
uint32Indices,
mesh,
primitives[i],
i,
options
)
);
} }
const gltfMesh = { const gltfMesh = {
name: mesh.name, name: mesh.name,
primitives : gltfPrimitives primitives: gltfPrimitives,
}; };
const meshIndex = gltf.meshes.length; const meshIndex = gltf.meshes.length;
@ -517,7 +685,7 @@ function addMesh(gltf, materials, bufferState, uint32Indices, mesh, options) {
function addNode(gltf, name, meshIndex, parentIndex) { function addNode(gltf, name, meshIndex, parentIndex) {
const node = { const node = {
name: name, name: name,
mesh : meshIndex mesh: meshIndex,
}; };
const nodeIndex = gltf.nodes.length; const nodeIndex = gltf.nodes.length;

View File

@ -1,4 +1,4 @@
'use strict'; "use strict";
module.exports = getBufferPadded; module.exports = getBufferPadded;
/** /**
@ -16,7 +16,7 @@ function getBufferPadded(buffer) {
if (remainder === 0) { if (remainder === 0) {
return buffer; return buffer;
} }
const padding = (remainder === 0) ? 0 : boundary - remainder; const padding = remainder === 0 ? 0 : boundary - remainder;
const emptyBuffer = Buffer.alloc(padding); const emptyBuffer = Buffer.alloc(padding);
return Buffer.concat([buffer, emptyBuffer]); return Buffer.concat([buffer, emptyBuffer]);
} }

View File

@ -1,4 +1,4 @@
'use strict'; "use strict";
module.exports = getJsonBufferPadded; module.exports = getJsonBufferPadded;
/** /**
@ -18,10 +18,10 @@ function getJsonBufferPadded(json) {
const boundary = 4; const boundary = 4;
const byteLength = Buffer.byteLength(string); const byteLength = Buffer.byteLength(string);
const remainder = byteLength % boundary; const remainder = byteLength % boundary;
const padding = (remainder === 0) ? 0 : boundary - remainder; const padding = remainder === 0 ? 0 : boundary - remainder;
let whitespace = ''; let whitespace = "";
for (let i = 0; i < padding; ++i) { for (let i = 0; i < padding; ++i) {
whitespace += ' '; whitespace += " ";
} }
string += whitespace; string += whitespace;

View File

@ -1,6 +1,6 @@
'use strict'; "use strict";
const Cesium = require('cesium'); const Cesium = require("cesium");
const getJsonBufferPadded = require('./getJsonBufferPadded'); const getJsonBufferPadded = require("./getJsonBufferPadded");
const defined = Cesium.defined; const defined = Cesium.defined;
@ -32,7 +32,7 @@ function gltfToGlb(gltf, binaryBuffer) {
// Write binary glTF header (magic, version, length) // Write binary glTF header (magic, version, length)
let byteOffset = 0; let byteOffset = 0;
glb.writeUInt32LE(0x46546C67, byteOffset); glb.writeUInt32LE(0x46546c67, byteOffset);
byteOffset += 4; byteOffset += 4;
glb.writeUInt32LE(2, byteOffset); glb.writeUInt32LE(2, byteOffset);
byteOffset += 4; byteOffset += 4;
@ -42,7 +42,7 @@ function gltfToGlb(gltf, binaryBuffer) {
// Write JSON Chunk header (length, type) // Write JSON Chunk header (length, type)
glb.writeUInt32LE(jsonBuffer.length, byteOffset); glb.writeUInt32LE(jsonBuffer.length, byteOffset);
byteOffset += 4; byteOffset += 4;
glb.writeUInt32LE(0x4E4F534A, byteOffset); // JSON glb.writeUInt32LE(0x4e4f534a, byteOffset); // JSON
byteOffset += 4; byteOffset += 4;
// Write JSON Chunk // Write JSON Chunk
@ -52,7 +52,7 @@ function gltfToGlb(gltf, binaryBuffer) {
// Write Binary Chunk header (length, type) // Write Binary Chunk header (length, type)
glb.writeUInt32LE(binaryBuffer.length, byteOffset); glb.writeUInt32LE(binaryBuffer.length, byteOffset);
byteOffset += 4; byteOffset += 4;
glb.writeUInt32LE(0x004E4942, byteOffset); // BIN glb.writeUInt32LE(0x004e4942, byteOffset); // BIN
byteOffset += 4; byteOffset += 4;
// Write Binary Chunk // Write Binary Chunk

View File

@ -1,11 +1,11 @@
'use strict'; "use strict";
const Cesium = require('cesium'); const Cesium = require("cesium");
const path = require('path'); const path = require("path");
const Promise = require('bluebird'); const Promise = require("bluebird");
const loadTexture = require('./loadTexture'); const loadTexture = require("./loadTexture");
const outsideDirectory = require('./outsideDirectory'); const outsideDirectory = require("./outsideDirectory");
const readLines = require('./readLines'); const readLines = require("./readLines");
const Texture = require('./Texture'); const Texture = require("./Texture");
const CesiumMath = Cesium.Math; const CesiumMath = Cesium.Math;
const clone = Cesium.clone; const clone = Cesium.clone;
@ -41,9 +41,18 @@ function loadMtl(mtlPath, options) {
const texturePromises = []; const texturePromises = [];
const overridingTextures = options.overridingTextures; const overridingTextures = options.overridingTextures;
const overridingSpecularTexture = defaultValue(overridingTextures.metallicRoughnessOcclusionTexture, overridingTextures.specularGlossinessTexture); const overridingSpecularTexture = defaultValue(
const overridingSpecularShininessTexture = defaultValue(overridingTextures.metallicRoughnessOcclusionTexture, overridingTextures.specularGlossinessTexture); overridingTextures.metallicRoughnessOcclusionTexture,
const overridingAmbientTexture = defaultValue(overridingTextures.metallicRoughnessOcclusionTexture, overridingTextures.occlusionTexture); overridingTextures.specularGlossinessTexture
);
const overridingSpecularShininessTexture = defaultValue(
overridingTextures.metallicRoughnessOcclusionTexture,
overridingTextures.specularGlossinessTexture
);
const overridingAmbientTexture = defaultValue(
overridingTextures.metallicRoughnessOcclusionTexture,
overridingTextures.occlusionTexture
);
const overridingNormalTexture = overridingTextures.normalTexture; const overridingNormalTexture = overridingTextures.normalTexture;
const overridingDiffuseTexture = overridingTextures.baseColorTexture; const overridingDiffuseTexture = overridingTextures.baseColorTexture;
const overridingEmissiveTexture = overridingTextures.emissiveTexture; const overridingEmissiveTexture = overridingTextures.emissiveTexture;
@ -51,20 +60,30 @@ function loadMtl(mtlPath, options) {
// Textures that are packed into PBR textures need to be decoded first // Textures that are packed into PBR textures need to be decoded first
const decodeOptions = { const decodeOptions = {
decode : true decode: true,
}; };
const diffuseTextureOptions = { const diffuseTextureOptions = {
checkTransparency : options.checkTransparency checkTransparency: options.checkTransparency,
}; };
const ambientTextureOptions = defined(overridingAmbientTexture) ? undefined : (options.packOcclusion ? decodeOptions : undefined); const ambientTextureOptions = defined(overridingAmbientTexture)
const specularTextureOptions = defined(overridingSpecularTexture) ? undefined : decodeOptions; ? undefined
const specularShinessTextureOptions = defined(overridingSpecularShininessTexture) ? undefined : decodeOptions; : options.packOcclusion
? decodeOptions
: undefined;
const specularTextureOptions = defined(overridingSpecularTexture)
? undefined
: decodeOptions;
const specularShinessTextureOptions = defined(
overridingSpecularShininessTexture
)
? undefined
: decodeOptions;
const emissiveTextureOptions = undefined; const emissiveTextureOptions = undefined;
const normalTextureOptions = undefined; const normalTextureOptions = undefined;
const alphaTextureOptions = { const alphaTextureOptions = {
decode : true decode: true,
}; };
function createMaterial(name) { function createMaterial(name) {
@ -88,7 +107,7 @@ function loadMtl(mtlPath, options) {
if (re.test(texturePath)) { if (re.test(texturePath)) {
texturePath = texturePath.split(/\s+/).pop(); texturePath = texturePath.split(/\s+/).pop();
} }
texturePath = texturePath.replace(/\\/g, '/'); texturePath = texturePath.replace(/\\/g, "/");
return path.normalize(path.resolve(mtlDirectory, texturePath)); return path.normalize(path.resolve(mtlDirectory, texturePath));
} }
@ -98,36 +117,36 @@ function loadMtl(mtlPath, options) {
const name = line.substring(7).trim(); const name = line.substring(7).trim();
createMaterial(name); createMaterial(name);
} else if (/^Ka /i.test(line)) { } else if (/^Ka /i.test(line)) {
values = line.substring(3).trim().split(' '); values = line.substring(3).trim().split(" ");
material.ambientColor = [ material.ambientColor = [
parseFloat(values[0]), parseFloat(values[0]),
parseFloat(values[1]), parseFloat(values[1]),
parseFloat(values[2]), parseFloat(values[2]),
1.0 1.0,
]; ];
} else if (/^Ke /i.test(line)) { } else if (/^Ke /i.test(line)) {
values = line.substring(3).trim().split(' '); values = line.substring(3).trim().split(" ");
material.emissiveColor = [ material.emissiveColor = [
parseFloat(values[0]), parseFloat(values[0]),
parseFloat(values[1]), parseFloat(values[1]),
parseFloat(values[2]), parseFloat(values[2]),
1.0 1.0,
]; ];
} else if (/^Kd /i.test(line)) { } else if (/^Kd /i.test(line)) {
values = line.substring(3).trim().split(' '); values = line.substring(3).trim().split(" ");
material.diffuseColor = [ material.diffuseColor = [
parseFloat(values[0]), parseFloat(values[0]),
parseFloat(values[1]), parseFloat(values[1]),
parseFloat(values[2]), parseFloat(values[2]),
1.0 1.0,
]; ];
} else if (/^Ks /i.test(line)) { } else if (/^Ks /i.test(line)) {
values = line.substring(3).trim().split(' '); values = line.substring(3).trim().split(" ");
material.specularColor = [ material.specularColor = [
parseFloat(values[0]), parseFloat(values[0]),
parseFloat(values[1]), parseFloat(values[1]),
parseFloat(values[2]), parseFloat(values[2]),
1.0 1.0,
]; ];
} else if (/^Ns /i.test(line)) { } else if (/^Ns /i.test(line)) {
value = line.substring(3).trim(); value = line.substring(3).trim();
@ -140,38 +159,61 @@ function loadMtl(mtlPath, options) {
material.alpha = correctAlpha(1.0 - parseFloat(value)); material.alpha = correctAlpha(1.0 - parseFloat(value));
} else if (/^map_Ka /i.test(line)) { } else if (/^map_Ka /i.test(line)) {
if (!defined(overridingAmbientTexture)) { if (!defined(overridingAmbientTexture)) {
material.ambientTexture = normalizeTexturePath(line.substring(7).trim(), mtlDirectory); material.ambientTexture = normalizeTexturePath(
line.substring(7).trim(),
mtlDirectory
);
} }
} else if (/^map_Ke /i.test(line)) { } else if (/^map_Ke /i.test(line)) {
if (!defined(overridingEmissiveTexture)) { if (!defined(overridingEmissiveTexture)) {
material.emissiveTexture = normalizeTexturePath(line.substring(7).trim(), mtlDirectory); material.emissiveTexture = normalizeTexturePath(
line.substring(7).trim(),
mtlDirectory
);
} }
} else if (/^map_Kd /i.test(line)) { } else if (/^map_Kd /i.test(line)) {
if (!defined(overridingDiffuseTexture)) { if (!defined(overridingDiffuseTexture)) {
material.diffuseTexture = normalizeTexturePath(line.substring(7).trim(), mtlDirectory); material.diffuseTexture = normalizeTexturePath(
line.substring(7).trim(),
mtlDirectory
);
} }
} else if (/^map_Ks /i.test(line)) { } else if (/^map_Ks /i.test(line)) {
if (!defined(overridingSpecularTexture)) { if (!defined(overridingSpecularTexture)) {
material.specularTexture = normalizeTexturePath(line.substring(7).trim(), mtlDirectory); material.specularTexture = normalizeTexturePath(
line.substring(7).trim(),
mtlDirectory
);
} }
} else if (/^map_Ns /i.test(line)) { } else if (/^map_Ns /i.test(line)) {
if (!defined(overridingSpecularShininessTexture)) { if (!defined(overridingSpecularShininessTexture)) {
material.specularShininessTexture = normalizeTexturePath(line.substring(7).trim(), mtlDirectory); material.specularShininessTexture = normalizeTexturePath(
line.substring(7).trim(),
mtlDirectory
);
} }
} else if (/^map_Bump /i.test(line)) { } else if (/^map_Bump /i.test(line)) {
if (!defined(overridingNormalTexture)) { if (!defined(overridingNormalTexture)) {
material.normalTexture = normalizeTexturePath(line.substring(9).trim(), mtlDirectory); material.normalTexture = normalizeTexturePath(
line.substring(9).trim(),
mtlDirectory
);
} }
} else if (/^map_d /i.test(line)) { } else if (/^map_d /i.test(line)) {
if (!defined(overridingAlphaTexture)) { if (!defined(overridingAlphaTexture)) {
material.alphaTexture = normalizeTexturePath(line.substring(6).trim(), mtlDirectory); material.alphaTexture = normalizeTexturePath(
line.substring(6).trim(),
mtlDirectory
);
} }
} }
} }
function loadMaterialTextures(material) { function loadMaterialTextures(material) {
// If an alpha texture is present the diffuse texture needs to be decoded so they can be packed together // If an alpha texture is present the diffuse texture needs to be decoded so they can be packed together
const diffuseAlphaTextureOptions = defined(material.alphaTexture) ? alphaTextureOptions : diffuseTextureOptions; const diffuseAlphaTextureOptions = defined(material.alphaTexture)
? alphaTextureOptions
: diffuseTextureOptions;
if (material.diffuseTexture === material.ambientTexture) { if (material.diffuseTexture === material.ambientTexture) {
// OBJ models are often exported with the same texture in the diffuse and ambient slots but this is typically not desirable, particularly // OBJ models are often exported with the same texture in the diffuse and ambient slots but this is typically not desirable, particularly
@ -179,8 +221,24 @@ function loadMtl(mtlPath, options) {
material.ambientTexture = undefined; material.ambientTexture = undefined;
} }
const textureNames = ['diffuseTexture', 'ambientTexture', 'emissiveTexture', 'specularTexture', 'specularShininessTexture', 'normalTexture', 'alphaTexture']; const textureNames = [
const textureOptions = [diffuseAlphaTextureOptions, ambientTextureOptions, emissiveTextureOptions, specularTextureOptions, specularShinessTextureOptions, normalTextureOptions, alphaTextureOptions]; "diffuseTexture",
"ambientTexture",
"emissiveTexture",
"specularTexture",
"specularShininessTexture",
"normalTexture",
"alphaTexture",
];
const textureOptions = [
diffuseAlphaTextureOptions,
ambientTextureOptions,
emissiveTextureOptions,
specularTextureOptions,
specularShinessTextureOptions,
normalTextureOptions,
alphaTextureOptions,
];
const sharedOptions = {}; const sharedOptions = {};
textureNames.forEach(function (name, index) { textureNames.forEach(function (name, index) {
@ -191,16 +249,28 @@ function loadMtl(mtlPath, options) {
sharedOptions[texturePath] = clone(originalOptions); sharedOptions[texturePath] = clone(originalOptions);
} }
const options = sharedOptions[texturePath]; const options = sharedOptions[texturePath];
options.checkTransparency = options.checkTransparency || originalOptions.checkTransparency; options.checkTransparency =
options.checkTransparency || originalOptions.checkTransparency;
options.decode = options.decode || originalOptions.decode; options.decode = options.decode || originalOptions.decode;
options.keepSource = options.keepSource || !originalOptions.decode || !originalOptions.checkTransparency; options.keepSource =
options.keepSource ||
!originalOptions.decode ||
!originalOptions.checkTransparency;
} }
}); });
textureNames.forEach(function (name) { textureNames.forEach(function (name) {
const texturePath = material[name]; const texturePath = material[name];
if (defined(texturePath)) { if (defined(texturePath)) {
loadMaterialTexture(material, name, sharedOptions[texturePath], mtlDirectory, texturePromiseMap, texturePromises, options); loadMaterialTexture(
material,
name,
sharedOptions[texturePath],
mtlDirectory,
texturePromiseMap,
texturePromises,
options
);
} }
}); });
} }
@ -249,7 +319,15 @@ loadMtl._createMaterial = function(materialOptions, options) {
return convertMaterial(combine(materialOptions, new Material()), options); return convertMaterial(combine(materialOptions, new Material()), options);
}; };
function loadMaterialTexture(material, name, textureOptions, mtlDirectory, texturePromiseMap, texturePromises, options) { function loadMaterialTexture(
material,
name,
textureOptions,
mtlDirectory,
texturePromiseMap,
texturePromises,
options
) {
const texturePath = material[name]; const texturePath = material[name];
if (!defined(texturePath)) { if (!defined(texturePath)) {
return; return;
@ -260,32 +338,48 @@ function loadMaterialTexture(material, name, textureOptions, mtlDirectory, textu
const shallowPath = path.join(mtlDirectory, path.basename(texturePath)); const shallowPath = path.join(mtlDirectory, path.basename(texturePath));
if (options.secure && outsideDirectory(texturePath, mtlDirectory)) { if (options.secure && outsideDirectory(texturePath, mtlDirectory)) {
// Try looking for the texture in the same directory as the obj // Try looking for the texture in the same directory as the obj
options.logger('Texture file is outside of the mtl directory and the secure flag is true. Attempting to read the texture file from within the obj directory instead.'); options.logger(
texturePromise = loadTexture(shallowPath, textureOptions) "Texture file is outside of the mtl directory and the secure flag is true. Attempting to read the texture file from within the obj directory instead."
.catch(function(error) { );
texturePromise = loadTexture(shallowPath, textureOptions).catch(function (
error
) {
options.logger(error.message); options.logger(error.message);
options.logger('Could not read texture file at ' + shallowPath + '. This texture will be ignored'); options.logger(
"Could not read texture file at " +
shallowPath +
". This texture will be ignored"
);
}); });
} else { } else {
texturePromise = loadTexture(texturePath, textureOptions) texturePromise = loadTexture(texturePath, textureOptions)
.catch(function (error) { .catch(function (error) {
// Try looking for the texture in the same directory as the obj // Try looking for the texture in the same directory as the obj
options.logger(error.message); options.logger(error.message);
options.logger('Could not read texture file at ' + texturePath + '. Attempting to read the texture file from within the obj directory instead.'); options.logger(
"Could not read texture file at " +
texturePath +
". Attempting to read the texture file from within the obj directory instead."
);
return loadTexture(shallowPath, textureOptions); return loadTexture(shallowPath, textureOptions);
}) })
.catch(function (error) { .catch(function (error) {
options.logger(error.message); options.logger(error.message);
options.logger('Could not read texture file at ' + shallowPath + '. This texture will be ignored.'); options.logger(
"Could not read texture file at " +
shallowPath +
". This texture will be ignored."
);
}); });
} }
texturePromiseMap[texturePath] = texturePromise; texturePromiseMap[texturePath] = texturePromise;
} }
texturePromises.push(texturePromise texturePromises.push(
.then(function(texture) { texturePromise.then(function (texture) {
material[name] = texture; material[name] = texture;
})); })
);
} }
function convertMaterial(material, options) { function convertMaterial(material, options) {
@ -305,7 +399,14 @@ function convertMaterials(materials, options) {
}); });
} }
function resizeChannel(sourcePixels, sourceWidth, sourceHeight, targetPixels, targetWidth, targetHeight) { function resizeChannel(
sourcePixels,
sourceWidth,
sourceHeight,
targetPixels,
targetWidth,
targetHeight
) {
// Nearest neighbor sampling // Nearest neighbor sampling
const widthRatio = sourceWidth / targetWidth; const widthRatio = sourceWidth / targetWidth;
const heightRatio = sourceHeight / targetHeight; const heightRatio = sourceHeight / targetHeight;
@ -325,7 +426,13 @@ function resizeChannel(sourcePixels, sourceWidth, sourceHeight, targetPixels, ta
let scratchResizeChannel; let scratchResizeChannel;
function getTextureChannel(texture, index, targetWidth, targetHeight, targetChannel) { function getTextureChannel(
texture,
index,
targetWidth,
targetHeight,
targetChannel
) {
const pixels = texture.pixels; // RGBA const pixels = texture.pixels; // RGBA
const sourceWidth = texture.width; const sourceWidth = texture.width;
const sourceHeight = texture.height; const sourceHeight = texture.height;
@ -335,7 +442,10 @@ function getTextureChannel(texture, index, targetWidth, targetHeight, targetChan
// Allocate the scratchResizeChannel on demand if the texture needs to be resized // Allocate the scratchResizeChannel on demand if the texture needs to be resized
let sourceChannel = targetChannel; let sourceChannel = targetChannel;
if (sourcePixelsLength > targetPixelsLength) { if (sourcePixelsLength > targetPixelsLength) {
if (!defined(scratchResizeChannel) || (sourcePixelsLength > scratchResizeChannel.length)) { if (
!defined(scratchResizeChannel) ||
sourcePixelsLength > scratchResizeChannel.length
) {
scratchResizeChannel = Buffer.alloc(sourcePixelsLength); scratchResizeChannel = Buffer.alloc(sourcePixelsLength);
} }
sourceChannel = scratchResizeChannel; sourceChannel = scratchResizeChannel;
@ -347,7 +457,14 @@ function getTextureChannel(texture, index, targetWidth, targetHeight, targetChan
} }
if (sourcePixelsLength > targetPixelsLength) { if (sourcePixelsLength > targetPixelsLength) {
resizeChannel(sourceChannel, sourceWidth, sourceHeight, targetChannel, targetWidth, targetHeight); resizeChannel(
sourceChannel,
sourceWidth,
sourceHeight,
targetChannel,
targetWidth,
targetHeight
);
} }
return targetChannel; return targetChannel;
@ -375,7 +492,19 @@ function getMinimumDimensions(textures, options) {
for (let i = 0; i < length; ++i) { for (let i = 0; i < length; ++i) {
const texture = textures[i]; const texture = textures[i];
if (texture.width !== width || texture.height !== height) { if (texture.width !== width || texture.height !== height) {
options.logger('Texture ' + texture.path + ' will be scaled from ' + texture.width + 'x' + texture.height + ' to ' + width + 'x' + height + '.'); options.logger(
"Texture " +
texture.path +
" will be scaled from " +
texture.width +
"x" +
texture.height +
" to " +
width +
"x" +
height +
"."
);
} }
} }
@ -410,7 +539,13 @@ function createDiffuseAlphaTexture(diffuseTexture, alphaTexture, options) {
} }
if (!defined(diffuseTexture.pixels) || !defined(alphaTexture.pixels)) { 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.'); 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; return diffuseTexture;
} }
@ -419,27 +554,57 @@ function createDiffuseAlphaTexture(diffuseTexture, alphaTexture, options) {
const width = dimensions[0]; const width = dimensions[0];
const height = dimensions[1]; const height = dimensions[1];
const pixelsLength = width * height; const pixelsLength = width * height;
const pixels = Buffer.alloc(pixelsLength * 4, 0xFF); // Initialize with 4 channels const pixels = Buffer.alloc(pixelsLength * 4, 0xff); // Initialize with 4 channels
const scratchChannel = Buffer.alloc(pixelsLength); const scratchChannel = Buffer.alloc(pixelsLength);
// Write into the R, G, B channels // Write into the R, G, B channels
const redChannel = getTextureChannel(diffuseTexture, 0, width, height, scratchChannel); const redChannel = getTextureChannel(
diffuseTexture,
0,
width,
height,
scratchChannel
);
writeChannel(pixels, redChannel, 0); writeChannel(pixels, redChannel, 0);
const greenChannel = getTextureChannel(diffuseTexture, 1, width, height, scratchChannel); const greenChannel = getTextureChannel(
diffuseTexture,
1,
width,
height,
scratchChannel
);
writeChannel(pixels, greenChannel, 1); writeChannel(pixels, greenChannel, 1);
const blueChannel = getTextureChannel(diffuseTexture, 2, width, height, scratchChannel); const blueChannel = getTextureChannel(
diffuseTexture,
2,
width,
height,
scratchChannel
);
writeChannel(pixels, blueChannel, 2); writeChannel(pixels, blueChannel, 2);
// First try reading the alpha component from the alpha channel, but if it is a single color read from the red channel instead. // First try reading the alpha component from the alpha channel, but if it is a single color read from the red channel instead.
let alphaChannel = getTextureChannel(alphaTexture, 3, width, height, scratchChannel); let alphaChannel = getTextureChannel(
alphaTexture,
3,
width,
height,
scratchChannel
);
if (isChannelSingleColor(alphaChannel)) { if (isChannelSingleColor(alphaChannel)) {
alphaChannel = getTextureChannel(alphaTexture, 0, width, height, scratchChannel); alphaChannel = getTextureChannel(
alphaTexture,
0,
width,
height,
scratchChannel
);
} }
writeChannel(pixels, alphaChannel, 3); writeChannel(pixels, alphaChannel, 3);
const texture = new Texture(); const texture = new Texture();
texture.name = diffuseTexture.name; texture.name = diffuseTexture.name;
texture.extension = '.png'; texture.extension = ".png";
texture.pixels = pixels; texture.pixels = pixels;
texture.width = width; texture.width = width;
texture.height = height; texture.height = height;
@ -448,7 +613,12 @@ function createDiffuseAlphaTexture(diffuseTexture, alphaTexture, options) {
return texture; return texture;
} }
function createMetallicRoughnessTexture(metallicTexture, roughnessTexture, occlusionTexture, options) { function createMetallicRoughnessTexture(
metallicTexture,
roughnessTexture,
occlusionTexture,
options
) {
if (defined(options.overridingTextures.metallicRoughnessOcclusionTexture)) { if (defined(options.overridingTextures.metallicRoughnessOcclusionTexture)) {
return metallicTexture; return metallicTexture;
} }
@ -462,21 +632,37 @@ function createMetallicRoughnessTexture(metallicTexture, roughnessTexture, occlu
} }
if (packMetallic && !defined(metallicTexture.pixels)) { if (packMetallic && !defined(metallicTexture.pixels)) {
options.logger('Could not get decoded texture data for ' + metallicTexture.path + '. The material will be created without a metallicRoughness texture.'); options.logger(
"Could not get decoded texture data for " +
metallicTexture.path +
". The material will be created without a metallicRoughness texture."
);
return undefined; return undefined;
} }
if (packRoughness && !defined(roughnessTexture.pixels)) { if (packRoughness && !defined(roughnessTexture.pixels)) {
options.logger('Could not get decoded texture data for ' + roughnessTexture.path + '. The material will be created without a metallicRoughness texture.'); options.logger(
"Could not get decoded texture data for " +
roughnessTexture.path +
". The material will be created without a metallicRoughness texture."
);
return undefined; return undefined;
} }
if (packOcclusion && !defined(occlusionTexture.pixels)) { if (packOcclusion && !defined(occlusionTexture.pixels)) {
options.logger('Could not get decoded texture data for ' + occlusionTexture.path + '. The occlusion texture will not be packed in the metallicRoughness texture.'); options.logger(
"Could not get decoded texture data for " +
occlusionTexture.path +
". The occlusion texture will not be packed in the metallicRoughness texture."
);
return undefined; return undefined;
} }
const packedTextures = [metallicTexture, roughnessTexture, occlusionTexture].filter(function(texture) { const packedTextures = [
metallicTexture,
roughnessTexture,
occlusionTexture,
].filter(function (texture) {
return defined(texture) && defined(texture.pixels); return defined(texture) && defined(texture.pixels);
}); });
@ -484,24 +670,42 @@ function createMetallicRoughnessTexture(metallicTexture, roughnessTexture, occlu
const width = dimensions[0]; const width = dimensions[0];
const height = dimensions[1]; const height = dimensions[1];
const pixelsLength = width * height; const pixelsLength = width * height;
const pixels = Buffer.alloc(pixelsLength * 4, 0xFF); // Initialize with 4 channels, unused channels will be white const pixels = Buffer.alloc(pixelsLength * 4, 0xff); // Initialize with 4 channels, unused channels will be white
const scratchChannel = Buffer.alloc(pixelsLength); const scratchChannel = Buffer.alloc(pixelsLength);
if (packMetallic) { if (packMetallic) {
// Write into the B channel // Write into the B channel
const metallicChannel = getTextureChannel(metallicTexture, 0, width, height, scratchChannel); const metallicChannel = getTextureChannel(
metallicTexture,
0,
width,
height,
scratchChannel
);
writeChannel(pixels, metallicChannel, 2); writeChannel(pixels, metallicChannel, 2);
} }
if (packRoughness) { if (packRoughness) {
// Write into the G channel // Write into the G channel
const roughnessChannel = getTextureChannel(roughnessTexture, 0, width, height, scratchChannel); const roughnessChannel = getTextureChannel(
roughnessTexture,
0,
width,
height,
scratchChannel
);
writeChannel(pixels, roughnessChannel, 1); writeChannel(pixels, roughnessChannel, 1);
} }
if (packOcclusion) { if (packOcclusion) {
// Write into the R channel // Write into the R channel
const occlusionChannel = getTextureChannel(occlusionTexture, 0, width, height, scratchChannel); const occlusionChannel = getTextureChannel(
occlusionTexture,
0,
width,
height,
scratchChannel
);
writeChannel(pixels, occlusionChannel, 0); writeChannel(pixels, occlusionChannel, 0);
} }
@ -510,11 +714,11 @@ function createMetallicRoughnessTexture(metallicTexture, roughnessTexture, occlu
for (let i = 0; i < length; ++i) { for (let i = 0; i < length; ++i) {
names[i] = packedTextures[i].name; names[i] = packedTextures[i].name;
} }
const name = names.join('_'); const name = names.join("_");
const texture = new Texture(); const texture = new Texture();
texture.name = name; texture.name = name;
texture.extension = '.png'; texture.extension = ".png";
texture.pixels = pixels; texture.pixels = pixels;
texture.width = width; texture.width = width;
texture.height = height; texture.height = height;
@ -522,7 +726,11 @@ function createMetallicRoughnessTexture(metallicTexture, roughnessTexture, occlu
return texture; return texture;
} }
function createSpecularGlossinessTexture(specularTexture, glossinessTexture, options) { function createSpecularGlossinessTexture(
specularTexture,
glossinessTexture,
options
) {
if (defined(options.overridingTextures.specularGlossinessTexture)) { if (defined(options.overridingTextures.specularGlossinessTexture)) {
return specularTexture; return specularTexture;
} }
@ -535,16 +743,26 @@ function createSpecularGlossinessTexture(specularTexture, glossinessTexture, opt
} }
if (packSpecular && !defined(specularTexture.pixels)) { if (packSpecular && !defined(specularTexture.pixels)) {
options.logger('Could not get decoded texture data for ' + specularTexture.path + '. The material will be created without a specularGlossiness texture.'); options.logger(
"Could not get decoded texture data for " +
specularTexture.path +
". The material will be created without a specularGlossiness texture."
);
return undefined; return undefined;
} }
if (packGlossiness && !defined(glossinessTexture.pixels)) { if (packGlossiness && !defined(glossinessTexture.pixels)) {
options.logger('Could not get decoded texture data for ' + glossinessTexture.path + '. The material will be created without a specularGlossiness texture.'); options.logger(
"Could not get decoded texture data for " +
glossinessTexture.path +
". The material will be created without a specularGlossiness texture."
);
return undefined; return undefined;
} }
const packedTextures = [specularTexture, glossinessTexture].filter(function(texture) { const packedTextures = [specularTexture, glossinessTexture].filter(function (
texture
) {
return defined(texture) && defined(texture.pixels); return defined(texture) && defined(texture.pixels);
}); });
@ -552,22 +770,46 @@ function createSpecularGlossinessTexture(specularTexture, glossinessTexture, opt
const width = dimensions[0]; const width = dimensions[0];
const height = dimensions[1]; const height = dimensions[1];
const pixelsLength = width * height; const pixelsLength = width * height;
const pixels = Buffer.alloc(pixelsLength * 4, 0xFF); // Initialize with 4 channels, unused channels will be white const pixels = Buffer.alloc(pixelsLength * 4, 0xff); // Initialize with 4 channels, unused channels will be white
const scratchChannel = Buffer.alloc(pixelsLength); const scratchChannel = Buffer.alloc(pixelsLength);
if (packSpecular) { if (packSpecular) {
// Write into the R, G, B channels // Write into the R, G, B channels
const redChannel = getTextureChannel(specularTexture, 0, width, height, scratchChannel); const redChannel = getTextureChannel(
specularTexture,
0,
width,
height,
scratchChannel
);
writeChannel(pixels, redChannel, 0); writeChannel(pixels, redChannel, 0);
const greenChannel = getTextureChannel(specularTexture, 1, width, height, scratchChannel); const greenChannel = getTextureChannel(
specularTexture,
1,
width,
height,
scratchChannel
);
writeChannel(pixels, greenChannel, 1); writeChannel(pixels, greenChannel, 1);
const blueChannel = getTextureChannel(specularTexture, 2, width, height, scratchChannel); const blueChannel = getTextureChannel(
specularTexture,
2,
width,
height,
scratchChannel
);
writeChannel(pixels, blueChannel, 2); writeChannel(pixels, blueChannel, 2);
} }
if (packGlossiness) { if (packGlossiness) {
// Write into the A channel // Write into the A channel
const glossinessChannel = getTextureChannel(glossinessTexture, 0, width, height, scratchChannel); const glossinessChannel = getTextureChannel(
glossinessTexture,
0,
width,
height,
scratchChannel
);
writeChannel(pixels, glossinessChannel, 3); writeChannel(pixels, glossinessChannel, 3);
} }
@ -576,11 +818,11 @@ function createSpecularGlossinessTexture(specularTexture, glossinessTexture, opt
for (let i = 0; i < length; ++i) { for (let i = 0; i < length; ++i) {
names[i] = packedTextures[i].name; names[i] = packedTextures[i].name;
} }
const name = names.join('_'); const name = names.join("_");
const texture = new Texture(); const texture = new Texture();
texture.name = name; texture.name = name;
texture.extension = '.png'; texture.extension = ".png";
texture.pixels = pixels; texture.pixels = pixels;
texture.width = width; texture.width = width;
texture.height = height; texture.height = height;
@ -596,8 +838,16 @@ function createSpecularGlossinessMaterial(material, options) {
const alphaTexture = material.alphaTexture; const alphaTexture = material.alphaTexture;
const specularTexture = material.specularTexture; const specularTexture = material.specularTexture;
const glossinessTexture = material.specularShininessTexture; const glossinessTexture = material.specularShininessTexture;
const specularGlossinessTexture = createSpecularGlossinessTexture(specularTexture, glossinessTexture, options); const specularGlossinessTexture = createSpecularGlossinessTexture(
const diffuseAlphaTexture = createDiffuseAlphaTexture(diffuseTexture, alphaTexture, options); specularTexture,
glossinessTexture,
options
);
const diffuseAlphaTexture = createDiffuseAlphaTexture(
diffuseTexture,
alphaTexture,
options
);
let emissiveFactor = material.emissiveColor.slice(0, 3); let emissiveFactor = material.emissiveColor.slice(0, 3);
let diffuseFactor = material.diffuseColor; let diffuseFactor = material.diffuseColor;
@ -634,7 +884,7 @@ function createSpecularGlossinessMaterial(material, options) {
} }
const doubleSided = transparent; const doubleSided = transparent;
const alphaMode = transparent ? 'BLEND' : 'OPAQUE'; const alphaMode = transparent ? "BLEND" : "OPAQUE";
return { return {
name: material.name, name: material.name,
@ -644,15 +894,15 @@ function createSpecularGlossinessMaterial(material, options) {
specularGlossinessTexture: specularGlossinessTexture, specularGlossinessTexture: specularGlossinessTexture,
diffuseFactor: diffuseFactor, diffuseFactor: diffuseFactor,
specularFactor: specularFactor, specularFactor: specularFactor,
glossinessFactor : glossinessFactor glossinessFactor: glossinessFactor,
} },
}, },
emissiveTexture: emissiveTexture, emissiveTexture: emissiveTexture,
normalTexture: normalTexture, normalTexture: normalTexture,
occlusionTexture: occlusionTexture, occlusionTexture: occlusionTexture,
emissiveFactor: emissiveFactor, emissiveFactor: emissiveFactor,
alphaMode: alphaMode, alphaMode: alphaMode,
doubleSided : doubleSided doubleSided: doubleSided,
}; };
} }
@ -664,8 +914,17 @@ function createMetallicRoughnessMaterial(material, options) {
const alphaTexture = material.alphaTexture; const alphaTexture = material.alphaTexture;
const metallicTexture = material.specularTexture; const metallicTexture = material.specularTexture;
const roughnessTexture = material.specularShininessTexture; const roughnessTexture = material.specularShininessTexture;
const metallicRoughnessTexture = createMetallicRoughnessTexture(metallicTexture, roughnessTexture, occlusionTexture, options); const metallicRoughnessTexture = createMetallicRoughnessTexture(
const diffuseAlphaTexture = createDiffuseAlphaTexture(baseColorTexture, alphaTexture, options); metallicTexture,
roughnessTexture,
occlusionTexture,
options
);
const diffuseAlphaTexture = createDiffuseAlphaTexture(
baseColorTexture,
alphaTexture,
options
);
if (options.packOcclusion) { if (options.packOcclusion) {
occlusionTexture = metallicRoughnessTexture; occlusionTexture = metallicRoughnessTexture;
@ -706,7 +965,7 @@ function createMetallicRoughnessMaterial(material, options) {
} }
const doubleSided = transparent; const doubleSided = transparent;
const alphaMode = transparent ? 'BLEND' : 'OPAQUE'; const alphaMode = transparent ? "BLEND" : "OPAQUE";
return { return {
name: material.name, name: material.name,
@ -715,14 +974,14 @@ function createMetallicRoughnessMaterial(material, options) {
metallicRoughnessTexture: metallicRoughnessTexture, metallicRoughnessTexture: metallicRoughnessTexture,
baseColorFactor: baseColorFactor, baseColorFactor: baseColorFactor,
metallicFactor: metallicFactor, metallicFactor: metallicFactor,
roughnessFactor : roughnessFactor roughnessFactor: roughnessFactor,
}, },
emissiveTexture: emissiveTexture, emissiveTexture: emissiveTexture,
normalTexture: normalTexture, normalTexture: normalTexture,
occlusionTexture: occlusionTexture, occlusionTexture: occlusionTexture,
emissiveFactor: emissiveFactor, emissiveFactor: emissiveFactor,
alphaMode: alphaMode, alphaMode: alphaMode,
doubleSided : doubleSided doubleSided: doubleSided,
}; };
} }
@ -745,11 +1004,16 @@ function convertTraditionalToMetallicRoughness(material) {
// Low specular intensity values should produce a rough material even if shininess is high. // Low specular intensity values should produce a rough material even if shininess is high.
if (specularIntensity < 0.1) { if (specularIntensity < 0.1) {
roughnessFactor *= (1.0 - specularIntensity); roughnessFactor *= 1.0 - specularIntensity;
} }
const metallicFactor = 0.0; const metallicFactor = 0.0;
material.specularColor = [metallicFactor, metallicFactor, metallicFactor, 1.0]; material.specularColor = [
metallicFactor,
metallicFactor,
metallicFactor,
1.0,
];
material.specularShininess = roughnessFactor; material.specularShininess = roughnessFactor;
} }

View File

@ -1,12 +1,12 @@
'use strict'; "use strict";
const Cesium = require('cesium'); const Cesium = require("cesium");
const path = require('path'); const path = require("path");
const Promise = require('bluebird'); const Promise = require("bluebird");
const ArrayStorage = require('./ArrayStorage'); const ArrayStorage = require("./ArrayStorage");
const loadMtl = require('./loadMtl'); const loadMtl = require("./loadMtl");
const outsideDirectory = require('./outsideDirectory'); const outsideDirectory = require("./outsideDirectory");
const readLines = require('./readLines'); const readLines = require("./readLines");
const Axis = Cesium.Axis; const Axis = Cesium.Axis;
const Cartesian3 = Cesium.Cartesian3; const Cartesian3 = Cesium.Cartesian3;
@ -44,8 +44,10 @@ function Primitive() {
} }
// OBJ regex patterns are modified from ThreeJS (https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/OBJLoader.js) // 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 vertexPattern =
const normalPattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // vn float float float /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 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 facePattern = /(-?\d+)\/?(-?\d*)\/?(-?\d*)/g; // for any face format "f v", "f v/v", "f v//v", "f v/v/v"
@ -61,7 +63,10 @@ const scratchCartesian = new Cartesian3();
* @private * @private
*/ */
function loadObj(objPath, options) { function loadObj(objPath, options) {
const axisTransform = getAxisTransform(options.inputUpAxis, options.outputUpAxis); const axisTransform = getAxisTransform(
options.inputUpAxis,
options.outputUpAxis
);
// Global store of vertex attributes listed in the obj file // Global store of vertex attributes listed in the obj file
let globalPositions = new ArrayStorage(ComponentDatatype.FLOAT); let globalPositions = new ArrayStorage(ComponentDatatype.FLOAT);
@ -87,7 +92,7 @@ function loadObj(objPath, options) {
let mtlPaths = []; let mtlPaths = [];
// Buffers for face data that spans multiple lines // Buffers for face data that spans multiple lines
let lineBuffer = ''; let lineBuffer = "";
// Used for parsing face data // Used for parsing face data
const faceVertices = []; const faceVertices = [];
@ -101,7 +106,7 @@ function loadObj(objPath, options) {
} }
function getName(name) { function getName(name) {
return (name === '' ? undefined : name); return name === "" ? undefined : name;
} }
function addNode(name) { function addNode(name) {
@ -154,7 +159,9 @@ function loadObj(objPath, options) {
const faceHasNormals = defined(normals[0]); const faceHasNormals = defined(normals[0]);
const primitiveHasUvs = primitive.uvs.length > 0; const primitiveHasUvs = primitive.uvs.length > 0;
const primitiveHasNormals = primitive.normals.length > 0; const primitiveHasNormals = primitive.normals.length > 0;
return primitiveHasUvs === faceHasUvs && primitiveHasNormals === faceHasNormals; return (
primitiveHasUvs === faceHasUvs && primitiveHasNormals === faceHasNormals
);
} }
function checkPrimitive(uvs, normals) { function checkPrimitive(uvs, normals) {
@ -170,18 +177,26 @@ function loadObj(objPath, options) {
const i = parseInt(index); const i = parseInt(index);
if (i < 0) { if (i < 0) {
// Negative vertex indexes reference the vertices immediately above it // Negative vertex indexes reference the vertices immediately above it
return (attributeData.length / components + i); return attributeData.length / components + i;
} }
return i - 1; return i - 1;
} }
function correctAttributeIndices(attributeIndices, attributeData, components) { function correctAttributeIndices(
attributeIndices,
attributeData,
components
) {
const length = attributeIndices.length; const length = attributeIndices.length;
for (let i = 0; i < length; ++i) { for (let i = 0; i < length; ++i) {
if (attributeIndices[i].length === 0) { if (attributeIndices[i].length === 0) {
attributeIndices[i] = undefined; attributeIndices[i] = undefined;
} else { } else {
attributeIndices[i] = getIndexFromStart(attributeIndices[i], attributeData, components); attributeIndices[i] = getIndexFromStart(
attributeIndices[i],
attributeData,
components
);
} }
} }
} }
@ -189,13 +204,18 @@ function loadObj(objPath, options) {
function correctVertices(vertices, positions, uvs, normals) { function correctVertices(vertices, positions, uvs, normals) {
const length = vertices.length; const length = vertices.length;
for (let i = 0; i < length; ++i) { for (let i = 0; i < length; ++i) {
vertices[i] = defaultValue(positions[i], '') + '/' + defaultValue(uvs[i], '') + '/' + defaultValue(normals[i], ''); vertices[i] =
defaultValue(positions[i], "") +
"/" +
defaultValue(uvs[i], "") +
"/" +
defaultValue(normals[i], "");
} }
} }
function createVertex(p, u, n) { function createVertex(p, u, n) {
// Positions // Positions
if (defined(p) && (globalPositions.length > 0)) { if (defined(p) && globalPositions.length > 0) {
if (p * 3 >= globalPositions.length) { if (p * 3 >= globalPositions.length) {
throw new RuntimeError(`Position index ${p} is out of bounds`); throw new RuntimeError(`Position index ${p} is out of bounds`);
} }
@ -208,7 +228,7 @@ function loadObj(objPath, options) {
} }
// Normals // Normals
if (defined(n) && (globalNormals.length > 0)) { if (defined(n) && globalNormals.length > 0) {
if (n * 3 >= globalNormals.length) { if (n * 3 >= globalNormals.length) {
throw new RuntimeError(`Normal index ${n} is out of bounds`); throw new RuntimeError(`Normal index ${n} is out of bounds`);
} }
@ -221,7 +241,7 @@ function loadObj(objPath, options) {
} }
// UVs // UVs
if (defined(u) && (globalUvs.length > 0)) { if (defined(u) && globalUvs.length > 0) {
if (u * 2 >= globalUvs.length) { if (u * 2 >= globalUvs.length) {
throw new RuntimeError(`UV index ${u} is out of bounds`); throw new RuntimeError(`UV index ${u} is out of bounds`);
} }
@ -272,11 +292,21 @@ function loadObj(objPath, options) {
const scratchAxis1 = new Cartesian3(); const scratchAxis1 = new Cartesian3();
const scratchAxis2 = new Cartesian3(); const scratchAxis2 = new Cartesian3();
const scratchNormal = new Cartesian3(); const scratchNormal = new Cartesian3();
const scratchPositions = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()]; const scratchPositions = [
new Cartesian3(),
new Cartesian3(),
new Cartesian3(),
new Cartesian3(),
];
const scratchVertexIndices = []; const scratchVertexIndices = [];
const scratchPoints = []; const scratchPoints = [];
function checkWindingCorrect(positionIndex1, positionIndex2, positionIndex3, normalIndex) { function checkWindingCorrect(
positionIndex1,
positionIndex2,
positionIndex3,
normalIndex
) {
if (!defined(normalIndex)) { if (!defined(normalIndex)) {
// If no face normal, we have to assume the winding is correct. // If no face normal, we have to assume the winding is correct.
return true; return true;
@ -290,7 +320,7 @@ function loadObj(objPath, options) {
const CA = Cartesian3.subtract(C, A, scratch5); const CA = Cartesian3.subtract(C, A, scratch5);
const cross = Cartesian3.cross(BA, CA, scratch3); const cross = Cartesian3.cross(BA, CA, scratch3);
return (Cartesian3.dot(normal, cross) >= 0); return Cartesian3.dot(normal, cross) >= 0;
} }
function addTriangle(index1, index2, index3, correctWinding) { function addTriangle(index1, index2, index3, correctWinding) {
@ -305,7 +335,13 @@ function loadObj(objPath, options) {
} }
} }
function addFace(vertices, positions, uvs, normals, triangleWindingOrderSanitization) { function addFace(
vertices,
positions,
uvs,
normals,
triangleWindingOrderSanitization
) {
correctAttributeIndices(positions, globalPositions, 3); correctAttributeIndices(positions, globalPositions, 3);
correctAttributeIndices(normals, globalNormals, 3); correctAttributeIndices(normals, globalNormals, 3);
correctAttributeIndices(uvs, globalUvs, 2); correctAttributeIndices(uvs, globalUvs, 2);
@ -314,12 +350,20 @@ function loadObj(objPath, options) {
checkPrimitive(uvs, faceNormals); checkPrimitive(uvs, faceNormals);
if (vertices.length === 3) { if (vertices.length === 3) {
const isWindingCorrect = !triangleWindingOrderSanitization || checkWindingCorrect(positions[0], positions[1], positions[2], normals[0]); const isWindingCorrect =
!triangleWindingOrderSanitization ||
checkWindingCorrect(
positions[0],
positions[1],
positions[2],
normals[0]
);
const index1 = addVertex(vertices[0], positions[0], uvs[0], normals[0]); const index1 = addVertex(vertices[0], positions[0], uvs[0], normals[0]);
const index2 = addVertex(vertices[1], positions[1], uvs[1], normals[1]); const index2 = addVertex(vertices[1], positions[1], uvs[1], normals[1]);
const index3 = addVertex(vertices[2], positions[2], uvs[2], normals[2]); const index3 = addVertex(vertices[2], positions[2], uvs[2], normals[2]);
addTriangle(index1, index2, index3, isWindingCorrect); addTriangle(index1, index2, index3, isWindingCorrect);
} else { // Triangulate if the face is not a triangle } else {
// Triangulate if the face is not a triangle
const points = scratchPoints; const points = scratchPoints;
const vertexIndices = scratchVertexIndices; const vertexIndices = scratchVertexIndices;
@ -335,17 +379,35 @@ function loadObj(objPath, options) {
points.push(getPosition(positions[i], scratchPositions[i])); points.push(getPosition(positions[i], scratchPositions[i]));
} }
const validGeometry = CoplanarPolygonGeometryLibrary.computeProjectTo2DArguments(points, scratchCenter, scratchAxis1, scratchAxis2); const validGeometry =
CoplanarPolygonGeometryLibrary.computeProjectTo2DArguments(
points,
scratchCenter,
scratchAxis1,
scratchAxis2
);
if (!validGeometry) { if (!validGeometry) {
return; return;
} }
const projectPoints = CoplanarPolygonGeometryLibrary.createProjectPointsTo2DFunction(scratchCenter, scratchAxis1, scratchAxis2); const projectPoints =
CoplanarPolygonGeometryLibrary.createProjectPointsTo2DFunction(
scratchCenter,
scratchAxis1,
scratchAxis2
);
const points2D = projectPoints(points); const points2D = projectPoints(points);
const indices = PolygonPipeline.triangulate(points2D); const indices = PolygonPipeline.triangulate(points2D);
const isWindingCorrect = PolygonPipeline.computeWindingOrder2D(points2D) !== WindingOrder.CLOCKWISE; const isWindingCorrect =
PolygonPipeline.computeWindingOrder2D(points2D) !==
WindingOrder.CLOCKWISE;
for (let i = 0; i < indices.length - 2; i += 3) { for (let i = 0; i < indices.length - 2; i += 3) {
addTriangle(vertexIndices[indices[i]], vertexIndices[indices[i+1]], vertexIndices[indices[i+2]], isWindingCorrect); addTriangle(
vertexIndices[indices[i]],
vertexIndices[indices[i + 1]],
vertexIndices[indices[i + 2]],
isWindingCorrect
);
} }
} }
} }
@ -354,7 +416,7 @@ function loadObj(objPath, options) {
line = line.trim(); line = line.trim();
let result; let result;
if ((line.length === 0) || (line.charAt(0) === '#')) { if (line.length === 0 || line.charAt(0) === "#") {
// Don't process empty lines or comments // Don't process empty lines or comments
} else if (/^o\s/i.test(line)) { } else if (/^o\s/i.test(line)) {
const objectName = line.substring(2).trim(); const objectName = line.substring(2).trim();
@ -380,7 +442,12 @@ function loadObj(objPath, options) {
globalPositions.push(position.y); globalPositions.push(position.y);
globalPositions.push(position.z); globalPositions.push(position.z);
} else if ((result = normalPattern.exec(line)) !== null) { } else if ((result = normalPattern.exec(line)) !== null) {
const normal = Cartesian3.fromElements(parseFloat(result[1]), parseFloat(result[2]), parseFloat(result[3]), scratchNormal); const normal = Cartesian3.fromElements(
parseFloat(result[1]),
parseFloat(result[2]),
parseFloat(result[3]),
scratchNormal
);
if (Cartesian3.equals(normal, Cartesian3.ZERO)) { if (Cartesian3.equals(normal, Cartesian3.ZERO)) {
Cartesian3.clone(Cartesian3.UNIT_Z, normal); Cartesian3.clone(Cartesian3.UNIT_Z, normal);
} else { } else {
@ -395,15 +462,16 @@ function loadObj(objPath, options) {
} else if ((result = uvPattern.exec(line)) !== null) { } else if ((result = uvPattern.exec(line)) !== null) {
globalUvs.push(parseFloat(result[1])); globalUvs.push(parseFloat(result[1]));
globalUvs.push(1.0 - parseFloat(result[2])); // Flip y so 0.0 is the bottom of the image globalUvs.push(1.0 - parseFloat(result[2])); // Flip y so 0.0 is the bottom of the image
} else { // face line or invalid line } else {
// face line or invalid line
// Because face lines can contain n vertices, we use a line buffer in case the face data spans multiple lines. // 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 there's a line continuation don't create face yet
if (line.slice(-1) === '\\') { if (line.slice(-1) === "\\") {
lineBuffer += line.substring(0, line.length - 1); lineBuffer += line.substring(0, line.length - 1);
return; return;
} }
lineBuffer += line; lineBuffer += line;
if (lineBuffer.substring(0, 2) === 'f ') { if (lineBuffer.substring(0, 2) === "f ") {
while ((result = facePattern.exec(lineBuffer)) !== null) { while ((result = facePattern.exec(lineBuffer)) !== null) {
faceVertices.push(result[0]); faceVertices.push(result[0]);
facePositions.push(result[1]); facePositions.push(result[1]);
@ -411,7 +479,13 @@ function loadObj(objPath, options) {
faceNormals.push(result[3]); faceNormals.push(result[3]);
} }
if (faceVertices.length > 2) { if (faceVertices.length > 2) {
addFace(faceVertices, facePositions, faceUvs, faceNormals, options.triangleWindingOrderSanitization); addFace(
faceVertices,
facePositions,
faceUvs,
faceNormals,
options.triangleWindingOrderSanitization
);
} }
faceVertices.length = 0; faceVertices.length = 0;
@ -419,7 +493,7 @@ function loadObj(objPath, options) {
faceNormals.length = 0; faceNormals.length = 0;
faceUvs.length = 0; faceUvs.length = 0;
} }
lineBuffer = ''; lineBuffer = "";
} }
} }
@ -427,29 +501,34 @@ function loadObj(objPath, options) {
addNode(); addNode();
// Parse the obj file // Parse the obj file
return readLines(objPath, parseLine) return readLines(objPath, parseLine).then(function () {
.then(function() {
// Unload resources // Unload resources
globalPositions = undefined; globalPositions = undefined;
globalNormals = undefined; globalNormals = undefined;
globalUvs = undefined; globalUvs = undefined;
// Load materials and textures // Load materials and textures
return finishLoading(nodes, mtlPaths, objPath, defined(activeMaterial), options); return finishLoading(
nodes,
mtlPaths,
objPath,
defined(activeMaterial),
options
);
}); });
} }
function getMtlPaths(mtllibLine) { function getMtlPaths(mtllibLine) {
// Handle paths with spaces. E.g. mtllib my material file.mtl // Handle paths with spaces. E.g. mtllib my material file.mtl
const mtlPaths = []; const mtlPaths = [];
const splits = mtllibLine.split(' '); const splits = mtllibLine.split(" ");
const length = splits.length; const length = splits.length;
let startIndex = 0; let startIndex = 0;
for (let i = 0; i < length; ++i) { for (let i = 0; i < length; ++i) {
if (path.extname(splits[i]) !== '.mtl') { if (path.extname(splits[i]) !== ".mtl") {
continue; continue;
} }
const mtlPath = splits.slice(startIndex, i + 1).join(' '); const mtlPath = splits.slice(startIndex, i + 1).join(" ");
mtlPaths.push(mtlPath); mtlPaths.push(mtlPath);
startIndex = i + 1; startIndex = i + 1;
} }
@ -459,11 +538,10 @@ function getMtlPaths(mtllibLine) {
function finishLoading(nodes, mtlPaths, objPath, usesMaterials, options) { function finishLoading(nodes, mtlPaths, objPath, usesMaterials, options) {
nodes = cleanNodes(nodes); nodes = cleanNodes(nodes);
if (nodes.length === 0) { if (nodes.length === 0) {
throw new RuntimeError(objPath + ' does not have any geometry data'); throw new RuntimeError(objPath + " does not have any geometry data");
} }
const name = path.basename(objPath, path.extname(objPath)); const name = path.basename(objPath, path.extname(objPath));
return loadMtls(mtlPaths, objPath, options) return loadMtls(mtlPaths, objPath, options).then(function (materials) {
.then(function(materials) {
if (materials.length > 0 && !usesMaterials) { if (materials.length > 0 && !usesMaterials) {
assignDefaultMaterial(nodes, materials, usesMaterials); assignDefaultMaterial(nodes, materials, usesMaterials);
} }
@ -471,13 +549,13 @@ function finishLoading(nodes, mtlPaths, objPath, usesMaterials, options) {
return { return {
nodes: nodes, nodes: nodes,
materials: materials, materials: materials,
name : name name: name,
}; };
}); });
} }
function normalizeMtlPath(mtlPath, objDirectory) { function normalizeMtlPath(mtlPath, objDirectory) {
mtlPath = mtlPath.replace(/\\/g, '/'); mtlPath = mtlPath.replace(/\\/g, "/");
return path.normalize(path.resolve(objDirectory, mtlPath)); return path.normalize(path.resolve(objDirectory, mtlPath));
} }
@ -490,19 +568,27 @@ function loadMtls(mtlPaths, objPath, options) {
return self.indexOf(value) === index; return self.indexOf(value) === index;
}); });
return Promise.map(mtlPaths, function(mtlPath) { return Promise.map(
mtlPaths,
function (mtlPath) {
mtlPath = normalizeMtlPath(mtlPath, objDirectory); mtlPath = normalizeMtlPath(mtlPath, objDirectory);
const shallowPath = path.join(objDirectory, path.basename(mtlPath)); const shallowPath = path.join(objDirectory, path.basename(mtlPath));
if (options.secure && outsideDirectory(mtlPath, objDirectory)) { if (options.secure && outsideDirectory(mtlPath, objDirectory)) {
// Try looking for the .mtl in the same directory as the obj // Try looking for the .mtl in the same directory as the obj
options.logger('The material file is outside of the obj directory and the secure flag is true. Attempting to read the material file from within the obj directory instead.'); options.logger(
"The material file is outside of the obj directory and the secure flag is true. Attempting to read the material file from within the obj directory instead."
);
return loadMtl(shallowPath, options) return loadMtl(shallowPath, options)
.then(function (materialsInMtl) { .then(function (materialsInMtl) {
materials = materials.concat(materialsInMtl); materials = materials.concat(materialsInMtl);
}) })
.catch(function (error) { .catch(function (error) {
options.logger(error.message); options.logger(error.message);
options.logger('Could not read material file at ' + shallowPath + '. Using default material instead.'); options.logger(
"Could not read material file at " +
shallowPath +
". Using default material instead."
);
}); });
} }
@ -510,7 +596,11 @@ function loadMtls(mtlPaths, objPath, options) {
.catch(function (error) { .catch(function (error) {
// Try looking for the .mtl in the same directory as the obj // Try looking for the .mtl in the same directory as the obj
options.logger(error.message); options.logger(error.message);
options.logger('Could not read material file at ' + mtlPath + '. Attempting to read the material file from within the obj directory instead.'); options.logger(
"Could not read material file at " +
mtlPath +
". Attempting to read the material file from within the obj directory instead."
);
return loadMtl(shallowPath, options); return loadMtl(shallowPath, options);
}) })
.then(function (materialsInMtl) { .then(function (materialsInMtl) {
@ -518,10 +608,15 @@ function loadMtls(mtlPaths, objPath, options) {
}) })
.catch(function (error) { .catch(function (error) {
options.logger(error.message); options.logger(error.message);
options.logger('Could not read material file at ' + shallowPath + '. Using default material instead.'); options.logger(
"Could not read material file at " +
shallowPath +
". Using default material instead."
);
}); });
}, {concurrency : 10}) },
.then(function() { { concurrency: 10 }
).then(function () {
return materials; return materials;
}); });
} }
@ -578,7 +673,7 @@ function removeEmptyMeshes(meshes) {
return primitive.indices.length > 0 && primitive.positions.length > 0; return primitive.indices.length > 0 && primitive.positions.length > 0;
}); });
// Valid meshes must have at least one primitive // Valid meshes must have at least one primitive
return (mesh.primitives.length > 0); return mesh.primitives.length > 0;
}); });
} }
@ -627,7 +722,7 @@ function setDefaultNames(items, defaultName, usedNames) {
const occurrences = usedNames[name]; const occurrences = usedNames[name];
if (defined(occurrences)) { if (defined(occurrences)) {
usedNames[name]++; usedNames[name]++;
name = name + '_' + occurrences; name = name + "_" + occurrences;
} else { } else {
usedNames[name] = 1; usedNames[name] = 1;
} }
@ -637,11 +732,11 @@ function setDefaultNames(items, defaultName, usedNames) {
function setDefaults(nodes) { function setDefaults(nodes) {
const usedNames = {}; const usedNames = {};
setDefaultNames(nodes, 'Node', usedNames); setDefaultNames(nodes, "Node", usedNames);
const nodesLength = nodes.length; const nodesLength = nodes.length;
for (let i = 0; i < nodesLength; ++i) { for (let i = 0; i < nodesLength; ++i) {
const node = nodes[i]; const node = nodes[i];
setDefaultNames(node.meshes, node.name + '-Mesh', usedNames); setDefaultNames(node.meshes, node.name + "-Mesh", usedNames);
} }
} }
@ -652,17 +747,17 @@ function cleanNodes(nodes) {
} }
function getAxisTransform(inputUpAxis, outputUpAxis) { function getAxisTransform(inputUpAxis, outputUpAxis) {
if (inputUpAxis === 'X' && outputUpAxis === 'Y') { if (inputUpAxis === "X" && outputUpAxis === "Y") {
return Axis.X_UP_TO_Y_UP; return Axis.X_UP_TO_Y_UP;
} else if (inputUpAxis === 'X' && outputUpAxis === 'Z') { } else if (inputUpAxis === "X" && outputUpAxis === "Z") {
return Axis.X_UP_TO_Z_UP; return Axis.X_UP_TO_Z_UP;
} else if (inputUpAxis === 'Y' && outputUpAxis === 'X') { } else if (inputUpAxis === "Y" && outputUpAxis === "X") {
return Axis.Y_UP_TO_X_UP; return Axis.Y_UP_TO_X_UP;
} else if (inputUpAxis === 'Y' && outputUpAxis === 'Z') { } else if (inputUpAxis === "Y" && outputUpAxis === "Z") {
return Axis.Y_UP_TO_Z_UP; return Axis.Y_UP_TO_Z_UP;
} else if (inputUpAxis === 'Z' && outputUpAxis === 'X') { } else if (inputUpAxis === "Z" && outputUpAxis === "X") {
return Axis.Z_UP_TO_X_UP; return Axis.Z_UP_TO_X_UP;
} else if (inputUpAxis === 'Z' && outputUpAxis === 'Y') { } else if (inputUpAxis === "Z" && outputUpAxis === "Y") {
return Axis.Z_UP_TO_Y_UP; return Axis.Z_UP_TO_Y_UP;
} }
} }

View File

@ -1,11 +1,11 @@
'use strict'; "use strict";
const Cesium = require('cesium'); const Cesium = require("cesium");
const fsExtra = require('fs-extra'); const fsExtra = require("fs-extra");
const jpeg = require('jpeg-js'); const jpeg = require("jpeg-js");
const path = require('path'); const path = require("path");
const PNG = require('pngjs').PNG; const PNG = require("pngjs").PNG;
const Promise = require('bluebird'); const Promise = require("bluebird");
const Texture = require('./Texture'); const Texture = require("./Texture");
const defaultValue = Cesium.defaultValue; const defaultValue = Cesium.defaultValue;
const defined = Cesium.defined; const defined = Cesium.defined;
@ -30,8 +30,7 @@ function loadTexture(texturePath, options) {
options.decode = defaultValue(options.decode, false); options.decode = defaultValue(options.decode, false);
options.keepSource = defaultValue(options.keepSource, false); options.keepSource = defaultValue(options.keepSource, false);
return fsExtra.readFile(texturePath) return fsExtra.readFile(texturePath).then(function (source) {
.then(function(source) {
const name = path.basename(texturePath, path.extname(texturePath)); const name = path.basename(texturePath, path.extname(texturePath));
const extension = path.extname(texturePath).toLowerCase(); const extension = path.extname(texturePath).toLowerCase();
const texture = new Texture(); const texture = new Texture();
@ -41,15 +40,14 @@ function loadTexture(texturePath, options) {
texture.path = texturePath; texture.path = texturePath;
let decodePromise; let decodePromise;
if (extension === '.png') { if (extension === ".png") {
decodePromise = decodePng(texture, options); decodePromise = decodePng(texture, options);
} else if (extension === '.jpg' || extension === '.jpeg') { } else if (extension === ".jpg" || extension === ".jpeg") {
decodePromise = decodeJpeg(texture, options); decodePromise = decodeJpeg(texture, options);
} }
if (defined(decodePromise)) { if (defined(decodePromise)) {
return decodePromise return decodePromise.then(function () {
.then(function() {
return texture; return texture;
}); });
} }
@ -101,12 +99,11 @@ function decodePng(texture, options) {
const colorType = source[25]; const colorType = source[25];
const channels = getChannels(colorType); const channels = getChannels(colorType);
const checkTransparency = (channels === 4 && options.checkTransparency); const checkTransparency = channels === 4 && options.checkTransparency;
const decode = options.decode || checkTransparency; const decode = options.decode || checkTransparency;
if (decode) { if (decode) {
return parsePng(source) return parsePng(source).then(function (decodedResults) {
.then(function(decodedResults) {
if (options.checkTransparency) { if (options.checkTransparency) {
texture.transparent = hasTransparency(decodedResults.data); texture.transparent = hasTransparency(decodedResults.data);
} }

View File

@ -1,10 +1,10 @@
'use strict'; "use strict";
const Cesium = require('cesium'); const Cesium = require("cesium");
const fsExtra = require('fs-extra'); const fsExtra = require("fs-extra");
const path = require('path'); const path = require("path");
const createGltf = require('./createGltf'); const createGltf = require("./createGltf");
const loadObj = require('./loadObj'); const loadObj = require("./loadObj");
const writeGltf = require('./writeGltf'); const writeGltf = require("./writeGltf");
const defaultValue = Cesium.defaultValue; const defaultValue = Cesium.defaultValue;
const defined = Cesium.defined; const defined = Cesium.defined;
@ -47,34 +47,72 @@ function obj2gltf(objPath, options) {
options = defaultValue(options, {}); options = defaultValue(options, {});
options.binary = defaultValue(options.binary, defaults.binary); options.binary = defaultValue(options.binary, defaults.binary);
options.separate = defaultValue(options.separate, defaults.separate); options.separate = defaultValue(options.separate, defaults.separate);
options.separateTextures = defaultValue(options.separateTextures, defaults.separateTextures) || options.separate; options.separateTextures =
options.checkTransparency = defaultValue(options.checkTransparency, defaults.checkTransparency); defaultValue(options.separateTextures, defaults.separateTextures) ||
options.separate;
options.checkTransparency = defaultValue(
options.checkTransparency,
defaults.checkTransparency
);
options.secure = defaultValue(options.secure, defaults.secure); options.secure = defaultValue(options.secure, defaults.secure);
options.packOcclusion = defaultValue(options.packOcclusion, defaults.packOcclusion); options.packOcclusion = defaultValue(
options.metallicRoughness = defaultValue(options.metallicRoughness, defaults.metallicRoughness); options.packOcclusion,
options.specularGlossiness = defaultValue(options.specularGlossiness, defaults.specularGlossiness); defaults.packOcclusion
);
options.metallicRoughness = defaultValue(
options.metallicRoughness,
defaults.metallicRoughness
);
options.specularGlossiness = defaultValue(
options.specularGlossiness,
defaults.specularGlossiness
);
options.unlit = defaultValue(options.unlit, defaults.unlit); options.unlit = defaultValue(options.unlit, defaults.unlit);
options.overridingTextures = defaultValue(options.overridingTextures, defaultValue.EMPTY_OBJECT); options.overridingTextures = defaultValue(
options.overridingTextures,
defaultValue.EMPTY_OBJECT
);
options.logger = defaultValue(options.logger, getDefaultLogger()); options.logger = defaultValue(options.logger, getDefaultLogger());
options.writer = defaultValue(options.writer, getDefaultWriter(options.outputDirectory)); options.writer = defaultValue(
options.writer,
getDefaultWriter(options.outputDirectory)
);
options.inputUpAxis = defaultValue(options.inputUpAxis, defaults.inputUpAxis); options.inputUpAxis = defaultValue(options.inputUpAxis, defaults.inputUpAxis);
options.outputUpAxis = defaultValue(options.outputUpAxis, defaults.outputUpAxis); options.outputUpAxis = defaultValue(
options.triangleWindingOrderSanitization = defaultValue(options.triangleWindingOrderSanitization, defaults.triangleWindingOrderSanitization); options.outputUpAxis,
defaults.outputUpAxis
);
options.triangleWindingOrderSanitization = defaultValue(
options.triangleWindingOrderSanitization,
defaults.triangleWindingOrderSanitization
);
if (!defined(objPath)) { if (!defined(objPath)) {
throw new DeveloperError('objPath is required'); throw new DeveloperError("objPath is required");
} }
if (options.separateTextures && !defined(options.writer)) { if (options.separateTextures && !defined(options.writer)) {
throw new DeveloperError('Either options.writer or options.outputDirectory must be defined when writing separate resources.'); throw new DeveloperError(
"Either options.writer or options.outputDirectory must be defined when writing separate resources."
);
} }
if (options.metallicRoughness + options.specularGlossiness + options.unlit > 1) { if (
throw new DeveloperError('Only one material type may be set from [metallicRoughness, specularGlossiness, unlit].'); options.metallicRoughness + options.specularGlossiness + options.unlit >
1
) {
throw new DeveloperError(
"Only one material type may be set from [metallicRoughness, specularGlossiness, unlit]."
);
} }
if (defined(options.overridingTextures.metallicRoughnessOcclusionTexture) && defined(options.overridingTextures.specularGlossinessTexture)) { if (
throw new DeveloperError('metallicRoughnessOcclusionTexture and specularGlossinessTexture cannot both be defined.'); defined(options.overridingTextures.metallicRoughnessOcclusionTexture) &&
defined(options.overridingTextures.specularGlossinessTexture)
) {
throw new DeveloperError(
"metallicRoughnessOcclusionTexture and specularGlossinessTexture cannot both be defined."
);
} }
if (defined(options.overridingTextures.metallicRoughnessOcclusionTexture)) { if (defined(options.overridingTextures.metallicRoughnessOcclusionTexture)) {
@ -176,19 +214,19 @@ obj2gltf.defaults = {
* @type String * @type String
* @default 'Y' * @default 'Y'
*/ */
inputUpAxis: 'Y', inputUpAxis: "Y",
/** /**
* Gets or sets the up axis of the converted glTF. * Gets or sets the up axis of the converted glTF.
* @type String * @type String
* @default 'Y' * @default 'Y'
*/ */
outputUpAxis: 'Y', outputUpAxis: "Y",
/** /**
* Gets or sets whether triangle winding order sanitization will be applied. * Gets or sets whether triangle winding order sanitization will be applied.
* @type Boolean * @type Boolean
* @default false * @default false
*/ */
windingOrderSanitization : false windingOrderSanitization: false,
}; };
/** /**

View File

@ -1,5 +1,5 @@
'use strict'; "use strict";
const path = require('path'); const path = require("path");
module.exports = outsideDirectory; module.exports = outsideDirectory;
@ -13,5 +13,5 @@ module.exports = outsideDirectory;
* @private * @private
*/ */
function outsideDirectory(file, directory) { function outsideDirectory(file, directory) {
return (path.relative(directory, file).indexOf('..') === 0); return path.relative(directory, file).indexOf("..") === 0;
} }

View File

@ -1,7 +1,7 @@
'use strict'; "use strict";
const fsExtra = require('fs-extra'); const fsExtra = require("fs-extra");
const Promise = require('bluebird'); const Promise = require("bluebird");
const readline = require('readline'); const readline = require("readline");
module.exports = readLines; module.exports = readLines;
@ -17,11 +17,11 @@ module.exports = readLines;
function readLines(path, callback) { function readLines(path, callback) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
const stream = fsExtra.createReadStream(path); const stream = fsExtra.createReadStream(path);
stream.on('error', reject); stream.on("error", reject);
stream.on('end', resolve); stream.on("end", resolve);
const lineReader = readline.createInterface({ const lineReader = readline.createInterface({
input : stream input: stream,
}); });
const callbackWrapper = function (line) { const callbackWrapper = function (line) {
@ -32,6 +32,6 @@ function readLines(path, callback) {
} }
}; };
lineReader.on('line', callbackWrapper); lineReader.on("line", callbackWrapper);
}); });
} }

View File

@ -1,10 +1,10 @@
'use strict'; "use strict";
const Cesium = require('cesium'); const Cesium = require("cesium");
const mime = require('mime'); const mime = require("mime");
const PNG = require('pngjs').PNG; const PNG = require("pngjs").PNG;
const Promise = require('bluebird'); const Promise = require("bluebird");
const getBufferPadded = require('./getBufferPadded'); const getBufferPadded = require("./getBufferPadded");
const gltfToGlb = require('./gltfToGlb'); const gltfToGlb = require("./gltfToGlb");
const defined = Cesium.defined; const defined = Cesium.defined;
const RuntimeError = Cesium.RuntimeError; const RuntimeError = Cesium.RuntimeError;
@ -21,8 +21,7 @@ module.exports = writeGltf;
* @private * @private
*/ */
function writeGltf(gltf, options) { function writeGltf(gltf, options) {
return encodeTextures(gltf) return encodeTextures(gltf).then(function () {
.then(function() {
const binary = options.binary; const binary = options.binary;
const separate = options.separate; const separate = options.separate;
const separateTextures = options.separateTextures; const separateTextures = options.separateTextures;
@ -42,8 +41,7 @@ function writeGltf(gltf, options) {
const binaryBuffer = gltf.buffers[0].extras._obj2gltf.source; const binaryBuffer = gltf.buffers[0].extras._obj2gltf.source;
return Promise.all(promises) return Promise.all(promises).then(function () {
.then(function() {
deleteExtras(gltf); deleteExtras(gltf);
removeEmpty(gltf); removeEmpty(gltf);
if (binary) { if (binary) {
@ -64,7 +62,7 @@ function encodePng(texture) {
height: texture.height, height: texture.height,
colorType: texture.transparent ? rgbaColorType : rgbColorType, colorType: texture.transparent ? rgbaColorType : rgbColorType,
inputColorType: rgbaColorType, inputColorType: rgbaColorType,
inputHasAlpha : true inputHasAlpha: true,
}); });
png.data = texture.pixels; png.data = texture.pixels;
@ -72,20 +70,23 @@ function encodePng(texture) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
const chunks = []; const chunks = [];
const stream = png.pack(); const stream = png.pack();
stream.on('data', function(chunk) { stream.on("data", function (chunk) {
chunks.push(chunk); chunks.push(chunk);
}); });
stream.on('end', function() { stream.on("end", function () {
resolve(Buffer.concat(chunks)); resolve(Buffer.concat(chunks));
}); });
stream.on('error', reject); stream.on("error", reject);
}); });
} }
function encodeTexture(texture) { function encodeTexture(texture) {
if (!defined(texture.source) && defined(texture.pixels) && texture.extension === '.png') { if (
return encodePng(texture) !defined(texture.source) &&
.then(function(encoded) { defined(texture.pixels) &&
texture.extension === ".png"
) {
return encodePng(texture).then(function (encoded) {
texture.source = encoded; texture.source = encoded;
}); });
} }
@ -118,9 +119,12 @@ function deleteExtras(gltf) {
function removeEmpty(json) { function removeEmpty(json) {
Object.keys(json).forEach(function (key) { Object.keys(json).forEach(function (key) {
if (!defined(json[key]) || (Array.isArray(json[key]) && json[key].length === 0)) { if (
!defined(json[key]) ||
(Array.isArray(json[key]) && json[key].length === 0)
) {
delete json[key]; // Delete values that are undefined or [] delete json[key]; // Delete values that are undefined or []
} else if (typeof json[key] === 'object') { } else if (typeof json[key] === "object") {
removeEmpty(json[key]); removeEmpty(json[key]);
} }
}); });
@ -128,22 +132,30 @@ function removeEmpty(json) {
function writeSeparateBuffers(gltf, options) { function writeSeparateBuffers(gltf, options) {
const buffers = gltf.buffers; const buffers = gltf.buffers;
return Promise.map(buffers, function(buffer) { return Promise.map(
buffers,
function (buffer) {
const source = buffer.extras._obj2gltf.source; const source = buffer.extras._obj2gltf.source;
const bufferUri = buffer.name + '.bin'; const bufferUri = buffer.name + ".bin";
buffer.uri = bufferUri; buffer.uri = bufferUri;
return options.writer(bufferUri, source); return options.writer(bufferUri, source);
}, {concurrency : 10}); },
{ concurrency: 10 }
);
} }
function writeSeparateTextures(gltf, options) { function writeSeparateTextures(gltf, options) {
const images = gltf.images; const images = gltf.images;
return Promise.map(images, function(image) { return Promise.map(
images,
function (image) {
const texture = image.extras._obj2gltf; const texture = image.extras._obj2gltf;
const imageUri = image.name + texture.extension; const imageUri = image.name + texture.extension;
image.uri = imageUri; image.uri = imageUri;
return options.writer(imageUri, texture.source); return options.writer(imageUri, texture.source);
}, {concurrency : 10}); },
{ concurrency: 10 }
);
} }
function writeEmbeddedBuffer(gltf) { function writeEmbeddedBuffer(gltf) {
@ -152,10 +164,13 @@ function writeEmbeddedBuffer(gltf) {
// Buffers larger than ~192MB cannot be base64 encoded due to a NodeJS limitation. Source: https://github.com/nodejs/node/issues/4266 // Buffers larger than ~192MB cannot be base64 encoded due to a NodeJS limitation. Source: https://github.com/nodejs/node/issues/4266
if (source.length > 201326580) { if (source.length > 201326580) {
throw new RuntimeError('Buffer is too large to embed in the glTF. Use the --separate flag instead.'); throw new RuntimeError(
"Buffer is too large to embed in the glTF. Use the --separate flag instead."
);
} }
buffer.uri = 'data:application/octet-stream;base64,' + source.toString('base64'); buffer.uri =
"data:application/octet-stream;base64," + source.toString("base64");
} }
function writeEmbeddedTextures(gltf) { function writeEmbeddedTextures(gltf) {
@ -178,7 +193,7 @@ function writeEmbeddedTextures(gltf) {
gltf.bufferViews.push({ gltf.bufferViews.push({
buffer: 0, buffer: 0,
byteOffset: byteOffset, byteOffset: byteOffset,
byteLength : textureByteLength byteLength: textureByteLength,
}); });
byteOffset += textureByteLength; byteOffset += textureByteLength;
sources.push(textureSource); sources.push(textureSource);

View File

@ -1,23 +1,26 @@
'use strict'; "use strict";
const Cesium = require('cesium'); const Cesium = require("cesium");
const obj2gltf = require('../../lib/obj2gltf'); const obj2gltf = require("../../lib/obj2gltf");
const createGltf = require('../../lib/createGltf'); const createGltf = require("../../lib/createGltf");
const loadObj = require('../../lib/loadObj'); const loadObj = require("../../lib/loadObj");
const { getDefaultMaterial } = require('../../lib/loadMtl'); const { getDefaultMaterial } = require("../../lib/loadMtl");
const clone = Cesium.clone; const clone = Cesium.clone;
const defined = Cesium.defined; const defined = Cesium.defined;
const WebGLConstants = Cesium.WebGLConstants; const WebGLConstants = Cesium.WebGLConstants;
const boxObjPath = 'specs/data/box/box.obj'; const boxObjPath = "specs/data/box/box.obj";
const groupObjPath = 'specs/data/box-objects-groups-materials/box-objects-groups-materials.obj'; const groupObjPath =
const complexObjPath = 'specs/data/box-complex-material/box-complex-material.obj'; "specs/data/box-objects-groups-materials/box-objects-groups-materials.obj";
const noMaterialsObjPath = 'specs/data/box-no-materials/box-no-materials.obj'; const complexObjPath =
const mixedAttributesObjPath = 'specs/data/box-mixed-attributes-2/box-mixed-attributes-2.obj'; "specs/data/box-complex-material/box-complex-material.obj";
const noMaterialsObjPath = "specs/data/box-no-materials/box-no-materials.obj";
const mixedAttributesObjPath =
"specs/data/box-mixed-attributes-2/box-mixed-attributes-2.obj";
let options; let options;
describe('createGltf', () => { describe("createGltf", () => {
let boxObjData; let boxObjData;
let groupObjData; let groupObjData;
let complexObjData; let complexObjData;
@ -36,7 +39,7 @@ describe('createGltf', () => {
mixedAttributesObjData = await loadObj(mixedAttributesObjPath, options); mixedAttributesObjData = await loadObj(mixedAttributesObjPath, options);
}); });
it('simple gltf', () => { it("simple gltf", () => {
const gltf = createGltf(boxObjData, options); const gltf = createGltf(boxObjData, options);
expect(gltf.materials.length).toBe(1); expect(gltf.materials.length).toBe(1);
@ -60,8 +63,8 @@ describe('createGltf', () => {
expect(indexAccessor.count).toBe(36); expect(indexAccessor.count).toBe(36);
}); });
it('does not combine buffers when that buffer would exceed the Node buffer size limit', () => { it("does not combine buffers when that buffer would exceed the Node buffer size limit", () => {
spyOn(createGltf, '_getBufferMaxByteLength').and.returnValue(0); spyOn(createGltf, "_getBufferMaxByteLength").and.returnValue(0);
const clonedOptions = clone(options, true); const clonedOptions = clone(options, true);
clonedOptions.separate = true; clonedOptions.separate = true;
@ -83,7 +86,7 @@ describe('createGltf', () => {
} }
}); });
it('multiple nodes, meshes, and primitives', () => { it("multiple nodes, meshes, and primitives", () => {
const gltf = createGltf(groupObjData, options); const gltf = createGltf(groupObjData, options);
expect(gltf.materials.length).toBe(3); expect(gltf.materials.length).toBe(3);
@ -102,22 +105,32 @@ describe('createGltf', () => {
} }
}); });
it('multiple textures', () => { it("multiple textures", () => {
const gltf = createGltf(complexObjData, options); const gltf = createGltf(complexObjData, options);
const material = gltf.materials[0]; const material = gltf.materials[0];
const pbr = material.pbrMetallicRoughness; const pbr = material.pbrMetallicRoughness;
const textures = [pbr.metallicRoughnessTexture, pbr.baseColorTexture, material.emissiveTexture, material.normalTexture, material.occlusionTexture]; const textures = [
expect(textures.map((texture) => { pbr.metallicRoughnessTexture,
pbr.baseColorTexture,
material.emissiveTexture,
material.normalTexture,
material.occlusionTexture,
];
expect(
textures
.map((texture) => {
return texture.index; return texture.index;
}).sort()).toEqual([0, 1, 2, 3, 4]); })
.sort()
).toEqual([0, 1, 2, 3, 4]);
expect(gltf.samplers[0]).toBeDefined(); expect(gltf.samplers[0]).toBeDefined();
}); });
it('creates default material', () => { it("creates default material", () => {
const gltf = createGltf(noMaterialsObjData, options); const gltf = createGltf(noMaterialsObjData, options);
const material = gltf.materials[0]; const material = gltf.materials[0];
const pbr = material.pbrMetallicRoughness; const pbr = material.pbrMetallicRoughness;
expect(material.name).toBe('default'); expect(material.name).toBe("default");
expect(pbr.baseColorTexture).toBeUndefined(); expect(pbr.baseColorTexture).toBeUndefined();
expect(pbr.metallicRoughnessTexture).toBeUndefined(); expect(pbr.metallicRoughnessTexture).toBeUndefined();
expect(pbr.baseColorFactor).toEqual([0.5, 0.5, 0.5, 1.0]); expect(pbr.baseColorFactor).toEqual([0.5, 0.5, 0.5, 1.0]);
@ -127,25 +140,29 @@ describe('createGltf', () => {
expect(material.normalTexture).toBeUndefined(); expect(material.normalTexture).toBeUndefined();
expect(material.ambientTexture).toBeUndefined(); expect(material.ambientTexture).toBeUndefined();
expect(material.emissiveFactor).toEqual([0.0, 0.0, 0.0]); expect(material.emissiveFactor).toEqual([0.0, 0.0, 0.0]);
expect(material.alphaMode).toBe('OPAQUE'); expect(material.alphaMode).toBe("OPAQUE");
expect(material.doubleSided).toBe(false); expect(material.doubleSided).toBe(false);
}); });
it('adds KHR_materials_pbrSpecularGlossiness extension when specularGlossiness is set', () => { it("adds KHR_materials_pbrSpecularGlossiness extension when specularGlossiness is set", () => {
options.specularGlossiness = true; options.specularGlossiness = true;
const gltf = createGltf(noMaterialsObjData, options); const gltf = createGltf(noMaterialsObjData, options);
expect(gltf.extensionsUsed).toEqual(['KHR_materials_pbrSpecularGlossiness']); expect(gltf.extensionsUsed).toEqual([
expect(gltf.extensionsRequired).toEqual(['KHR_materials_pbrSpecularGlossiness']); "KHR_materials_pbrSpecularGlossiness",
]);
expect(gltf.extensionsRequired).toEqual([
"KHR_materials_pbrSpecularGlossiness",
]);
}); });
it('adds KHR_materials_unlit extension when unlit is set', () => { it("adds KHR_materials_unlit extension when unlit is set", () => {
options.unlit = true; options.unlit = true;
const gltf = createGltf(noMaterialsObjData, options); const gltf = createGltf(noMaterialsObjData, options);
expect(gltf.extensionsUsed).toEqual(['KHR_materials_unlit']); expect(gltf.extensionsUsed).toEqual(["KHR_materials_unlit"]);
expect(gltf.extensionsRequired).toEqual(['KHR_materials_unlit']); expect(gltf.extensionsRequired).toEqual(["KHR_materials_unlit"]);
}); });
it('runs without normals', () => { it("runs without normals", () => {
boxObjData.nodes[0].meshes[0].primitives[0].normals.length = 0; boxObjData.nodes[0].meshes[0].primitives[0].normals.length = 0;
const gltf = createGltf(boxObjData, options); const gltf = createGltf(boxObjData, options);
@ -155,7 +172,7 @@ describe('createGltf', () => {
expect(attributes.TEXCOORD_0).toBeDefined(); expect(attributes.TEXCOORD_0).toBeDefined();
}); });
it('runs without uvs', () => { it("runs without uvs", () => {
boxObjData.nodes[0].meshes[0].primitives[0].uvs.length = 0; boxObjData.nodes[0].meshes[0].primitives[0].uvs.length = 0;
const gltf = createGltf(boxObjData, options); const gltf = createGltf(boxObjData, options);
@ -165,7 +182,7 @@ describe('createGltf', () => {
expect(attributes.TEXCOORD_0).toBeUndefined(); expect(attributes.TEXCOORD_0).toBeUndefined();
}); });
it('runs without uvs and normals', () => { it("runs without uvs and normals", () => {
boxObjData.nodes[0].meshes[0].primitives[0].normals.length = 0; boxObjData.nodes[0].meshes[0].primitives[0].normals.length = 0;
boxObjData.nodes[0].meshes[0].primitives[0].uvs.length = 0; boxObjData.nodes[0].meshes[0].primitives[0].uvs.length = 0;
@ -176,7 +193,7 @@ describe('createGltf', () => {
expect(attributes.TEXCOORD_0).toBeUndefined(); expect(attributes.TEXCOORD_0).toBeUndefined();
}); });
it('splits incompatible materials', () => { it("splits incompatible materials", () => {
const gltf = createGltf(mixedAttributesObjData, options); const gltf = createGltf(mixedAttributesObjData, options);
const materials = gltf.materials; const materials = gltf.materials;
const meshes = gltf.meshes; const meshes = gltf.meshes;
@ -184,11 +201,12 @@ describe('createGltf', () => {
const referenceMaterial = mixedAttributesObjData.materials[0]; const referenceMaterial = mixedAttributesObjData.materials[0];
delete referenceMaterial.name; delete referenceMaterial.name;
referenceMaterial.pbrMetallicRoughness.baseColorTexture = { referenceMaterial.pbrMetallicRoughness.baseColorTexture = {
index : 0 index: 0,
}; };
const referenceMaterialNoTextures = clone(referenceMaterial, true); const referenceMaterialNoTextures = clone(referenceMaterial, true);
referenceMaterialNoTextures.pbrMetallicRoughness.baseColorTexture = undefined; referenceMaterialNoTextures.pbrMetallicRoughness.baseColorTexture =
undefined;
const defaultMaterial = getDefaultMaterial(options); const defaultMaterial = getDefaultMaterial(options);
delete defaultMaterial.name; delete defaultMaterial.name;
@ -204,15 +222,15 @@ describe('createGltf', () => {
// * positions/normals // * positions/normals
// * positions/uvs // * positions/uvs
expect(materialNames).toEqual([ expect(materialNames).toEqual([
'default', "default",
'default-2', "default-2",
'default-3', "default-3",
'Material', "Material",
'Material-2', "Material-2",
'Material-3', "Material-3",
'Missing', "Missing",
'Missing-2', "Missing-2",
'Missing-3' "Missing-3",
]); ]);
expect(materials.length).toBe(9); expect(materials.length).toBe(9);
@ -236,7 +254,9 @@ describe('createGltf', () => {
const primitive = primitives[j]; const primitive = primitives[j];
const material = materials[primitive.material]; const material = materials[primitive.material];
if (!defined(primitive.attributes.TEXCOORD_0)) { if (!defined(primitive.attributes.TEXCOORD_0)) {
expect(material.pbrMetallicRoughness.baseColorTexture).toBeUndefined(); expect(
material.pbrMetallicRoughness.baseColorTexture
).toBeUndefined();
} }
} }
} }
@ -269,7 +289,7 @@ describe('createGltf', () => {
} }
} }
it('detects need to use uint32 indices', () => { it("detects need to use uint32 indices", () => {
expandObjData(boxObjData, 2731); // Right above 65536 limit expandObjData(boxObjData, 2731); // Right above 65536 limit
let primitive = boxObjData.nodes[0].meshes[0].primitives[0]; let primitive = boxObjData.nodes[0].meshes[0].primitives[0];
const indicesLength = primitive.indices.length; const indicesLength = primitive.indices.length;

View File

@ -1,31 +1,42 @@
'use strict'; "use strict";
const Cesium = require('cesium'); const Cesium = require("cesium");
const fsExtra = require('fs-extra'); const fsExtra = require("fs-extra");
const loadMtl = require('../../lib/loadMtl'); const loadMtl = require("../../lib/loadMtl");
const loadTexture = require('../../lib/loadTexture'); const loadTexture = require("../../lib/loadTexture");
const obj2gltf = require('../../lib/obj2gltf'); const obj2gltf = require("../../lib/obj2gltf");
const clone = Cesium.clone; const clone = Cesium.clone;
const coloredMaterialPath = 'specs/data/box/box.mtl'; const coloredMaterialPath = "specs/data/box/box.mtl";
const texturedMaterialPath = 'specs/data/box-complex-material/box-complex-material.mtl'; const texturedMaterialPath =
const texturedWithOptionsMaterialPath = 'specs/data/box-texture-options/box-texture-options.mtl'; "specs/data/box-complex-material/box-complex-material.mtl";
const multipleMaterialsPath = 'specs/data/box-multiple-materials/box-multiple-materials.mtl'; const texturedWithOptionsMaterialPath =
const externalMaterialPath = 'specs/data/box-external-resources/box-external-resources.mtl'; "specs/data/box-texture-options/box-texture-options.mtl";
const resourcesInRootMaterialPath = 'specs/data/box-resources-in-root/box-resources-in-root.mtl'; const multipleMaterialsPath =
const externalInRootMaterialPath = 'specs/data/box-external-resources-in-root/box-external-resources-in-root.mtl'; "specs/data/box-multiple-materials/box-multiple-materials.mtl";
const transparentMaterialPath = 'specs/data/box-transparent/box-transparent.mtl'; const externalMaterialPath =
const sharedTexturesMaterialPath = 'specs/data/box-shared-textures/box-shared-textures.mtl'; "specs/data/box-external-resources/box-external-resources.mtl";
const sharedTexturesMaterial2Path = 'specs/data/box-shared-textures-2/box-shared-textures-2.mtl'; const resourcesInRootMaterialPath =
"specs/data/box-resources-in-root/box-resources-in-root.mtl";
const externalInRootMaterialPath =
"specs/data/box-external-resources-in-root/box-external-resources-in-root.mtl";
const transparentMaterialPath =
"specs/data/box-transparent/box-transparent.mtl";
const sharedTexturesMaterialPath =
"specs/data/box-shared-textures/box-shared-textures.mtl";
const sharedTexturesMaterial2Path =
"specs/data/box-shared-textures-2/box-shared-textures-2.mtl";
const diffuseTexturePath = 'specs/data/box-textured/cesium.png'; const diffuseTexturePath = "specs/data/box-textured/cesium.png";
const transparentDiffuseTexturePath = 'specs/data/box-complex-material/diffuse.png'; const transparentDiffuseTexturePath =
const alphaTexturePath = 'specs/data/box-complex-material-alpha/alpha.png'; "specs/data/box-complex-material/diffuse.png";
const ambientTexturePath = 'specs/data/box-complex-material/ambient.gif'; const alphaTexturePath = "specs/data/box-complex-material-alpha/alpha.png";
const normalTexturePath = 'specs/data/box-complex-material/bump.png'; const ambientTexturePath = "specs/data/box-complex-material/ambient.gif";
const emissiveTexturePath = 'specs/data/box-complex-material/emission.jpg'; const normalTexturePath = "specs/data/box-complex-material/bump.png";
const specularTexturePath = 'specs/data/box-complex-material/specular.jpeg'; const emissiveTexturePath = "specs/data/box-complex-material/emission.jpg";
const specularShininessTexturePath = 'specs/data/box-complex-material/shininess.png'; const specularTexturePath = "specs/data/box-complex-material/specular.jpeg";
const specularShininessTexturePath =
"specs/data/box-complex-material/shininess.png";
let diffuseTexture; let diffuseTexture;
let transparentDiffuseTexture; let transparentDiffuseTexture;
@ -37,24 +48,30 @@ let specularTexture;
let specularShininessTexture; let specularShininessTexture;
const checkTransparencyOptions = { const checkTransparencyOptions = {
checkTransparency : true checkTransparency: true,
}; };
const decodeOptions = { const decodeOptions = {
decode : true decode: true,
}; };
let options; let options;
describe('loadMtl', () => { describe("loadMtl", () => {
beforeAll(async () => { beforeAll(async () => {
diffuseTexture = await loadTexture(diffuseTexturePath, decodeOptions); diffuseTexture = await loadTexture(diffuseTexturePath, decodeOptions);
transparentDiffuseTexture = await loadTexture(transparentDiffuseTexturePath, checkTransparencyOptions); transparentDiffuseTexture = await loadTexture(
transparentDiffuseTexturePath,
checkTransparencyOptions
);
alphaTexture = await loadTexture(alphaTexturePath, decodeOptions); alphaTexture = await loadTexture(alphaTexturePath, decodeOptions);
ambientTexture = await loadTexture(ambientTexturePath); ambientTexture = await loadTexture(ambientTexturePath);
normalTexture = await loadTexture(normalTexturePath); normalTexture = await loadTexture(normalTexturePath);
emissiveTexture = await loadTexture(emissiveTexturePath); emissiveTexture = await loadTexture(emissiveTexturePath);
specularTexture = await loadTexture(specularTexturePath, decodeOptions); specularTexture = await loadTexture(specularTexturePath, decodeOptions);
specularShininessTexture = await loadTexture(specularShininessTexturePath, decodeOptions); specularShininessTexture = await loadTexture(
specularShininessTexturePath,
decodeOptions
);
}); });
beforeEach(() => { beforeEach(() => {
@ -63,7 +80,7 @@ describe('loadMtl', () => {
options.logger = () => {}; options.logger = () => {};
}); });
it('loads mtl', async () => { it("loads mtl", async () => {
options.metallicRoughness = true; options.metallicRoughness = true;
const materials = await loadMtl(coloredMaterialPath, options); const materials = await loadMtl(coloredMaterialPath, options);
expect(materials.length).toBe(1); expect(materials.length).toBe(1);
@ -74,16 +91,16 @@ describe('loadMtl', () => {
expect(pbr.baseColorFactor).toEqual([0.64, 0.64, 0.64, 1.0]); expect(pbr.baseColorFactor).toEqual([0.64, 0.64, 0.64, 1.0]);
expect(pbr.metallicFactor).toBe(0.5); expect(pbr.metallicFactor).toBe(0.5);
expect(pbr.roughnessFactor).toBe(96.078431); expect(pbr.roughnessFactor).toBe(96.078431);
expect(material.name).toBe('Material'); expect(material.name).toBe("Material");
expect(material.emissiveTexture).toBeUndefined(); expect(material.emissiveTexture).toBeUndefined();
expect(material.normalTexture).toBeUndefined(); expect(material.normalTexture).toBeUndefined();
expect(material.ambientTexture).toBeUndefined(); expect(material.ambientTexture).toBeUndefined();
expect(material.emissiveFactor).toEqual([0.0, 0.0, 0.1]); expect(material.emissiveFactor).toEqual([0.0, 0.0, 0.1]);
expect(material.alphaMode).toBe('OPAQUE'); expect(material.alphaMode).toBe("OPAQUE");
expect(material.doubleSided).toBe(false); expect(material.doubleSided).toBe(false);
}); });
it('loads mtl with textures', async () => { it("loads mtl with textures", async () => {
options.metallicRoughness = true; options.metallicRoughness = true;
const materials = await loadMtl(texturedMaterialPath, options); const materials = await loadMtl(texturedMaterialPath, options);
expect(materials.length).toBe(1); expect(materials.length).toBe(1);
@ -94,16 +111,16 @@ describe('loadMtl', () => {
expect(pbr.baseColorFactor).toEqual([1.0, 1.0, 1.0, 0.9]); expect(pbr.baseColorFactor).toEqual([1.0, 1.0, 1.0, 0.9]);
expect(pbr.metallicFactor).toBe(1.0); expect(pbr.metallicFactor).toBe(1.0);
expect(pbr.roughnessFactor).toBe(1.0); expect(pbr.roughnessFactor).toBe(1.0);
expect(material.name).toBe('Material'); expect(material.name).toBe("Material");
expect(material.emissiveTexture).toBeDefined(); expect(material.emissiveTexture).toBeDefined();
expect(material.normalTexture).toBeDefined(); expect(material.normalTexture).toBeDefined();
expect(material.occlusionTexture).toBeDefined(); expect(material.occlusionTexture).toBeDefined();
expect(material.emissiveFactor).toEqual([1.0, 1.0, 1.0]); expect(material.emissiveFactor).toEqual([1.0, 1.0, 1.0]);
expect(material.alphaMode).toBe('BLEND'); expect(material.alphaMode).toBe("BLEND");
expect(material.doubleSided).toBe(true); expect(material.doubleSided).toBe(true);
}); });
it('loads mtl with textures having options', async () => { it("loads mtl with textures having options", async () => {
options.metallicRoughness = true; options.metallicRoughness = true;
const materials = await loadMtl(texturedWithOptionsMaterialPath, options); const materials = await loadMtl(texturedWithOptionsMaterialPath, options);
expect(materials.length).toBe(1); expect(materials.length).toBe(1);
@ -114,54 +131,60 @@ describe('loadMtl', () => {
expect(pbr.baseColorFactor).toEqual([1.0, 1.0, 1.0, 0.9]); expect(pbr.baseColorFactor).toEqual([1.0, 1.0, 1.0, 0.9]);
expect(pbr.metallicFactor).toBe(1.0); expect(pbr.metallicFactor).toBe(1.0);
expect(pbr.roughnessFactor).toBe(1.0); expect(pbr.roughnessFactor).toBe(1.0);
expect(material.name).toBe('Material'); expect(material.name).toBe("Material");
expect(material.emissiveTexture).toBeDefined(); expect(material.emissiveTexture).toBeDefined();
expect(material.normalTexture).toBeDefined(); expect(material.normalTexture).toBeDefined();
expect(material.occlusionTexture).toBeDefined(); expect(material.occlusionTexture).toBeDefined();
expect(material.emissiveFactor).toEqual([1.0, 1.0, 1.0]); expect(material.emissiveFactor).toEqual([1.0, 1.0, 1.0]);
expect(material.alphaMode).toBe('BLEND'); expect(material.alphaMode).toBe("BLEND");
expect(material.doubleSided).toBe(true); expect(material.doubleSided).toBe(true);
}); });
it('loads mtl with multiple materials', async () => { it("loads mtl with multiple materials", async () => {
options.metallicRoughness = true; options.metallicRoughness = true;
const materials = await loadMtl(multipleMaterialsPath, options); const materials = await loadMtl(multipleMaterialsPath, options);
expect(materials.length).toBe(3); expect(materials.length).toBe(3);
expect(materials[0].name).toBe('Blue'); expect(materials[0].name).toBe("Blue");
expect(materials[0].pbrMetallicRoughness.baseColorFactor).toEqual([0.0, 0.0, 0.64, 1.0]); expect(materials[0].pbrMetallicRoughness.baseColorFactor).toEqual([
expect(materials[1].name).toBe('Green'); 0.0, 0.0, 0.64, 1.0,
expect(materials[1].pbrMetallicRoughness.baseColorFactor).toEqual([0.0, 0.64, 0.0, 1.0]); ]);
expect(materials[2].name).toBe('Red'); expect(materials[1].name).toBe("Green");
expect(materials[2].pbrMetallicRoughness.baseColorFactor).toEqual([0.64, 0.0, 0.0, 1.0]); expect(materials[1].pbrMetallicRoughness.baseColorFactor).toEqual([
0.0, 0.64, 0.0, 1.0,
]);
expect(materials[2].name).toBe("Red");
expect(materials[2].pbrMetallicRoughness.baseColorFactor).toEqual([
0.64, 0.0, 0.0, 1.0,
]);
}); });
it('sets overriding textures', async () => { it("sets overriding textures", async () => {
spyOn(fsExtra, 'readFile').and.callThrough(); spyOn(fsExtra, "readFile").and.callThrough();
options.overridingTextures = { options.overridingTextures = {
metallicRoughnessOcclusionTexture: alphaTexturePath, metallicRoughnessOcclusionTexture: alphaTexturePath,
baseColorTexture: alphaTexturePath, baseColorTexture: alphaTexturePath,
emissiveTexture : emissiveTexturePath emissiveTexture: emissiveTexturePath,
}; };
const materials = await loadMtl(texturedMaterialPath, options); const materials = await loadMtl(texturedMaterialPath, options);
const material = materials[0]; const material = materials[0];
const pbr = material.pbrMetallicRoughness; const pbr = material.pbrMetallicRoughness;
expect(pbr.baseColorTexture.name).toBe('alpha'); expect(pbr.baseColorTexture.name).toBe("alpha");
expect(pbr.metallicRoughnessTexture.name).toBe('alpha'); expect(pbr.metallicRoughnessTexture.name).toBe("alpha");
expect(material.emissiveTexture.name).toBe('emission'); expect(material.emissiveTexture.name).toBe("emission");
expect(material.normalTexture.name).toBe('bump'); expect(material.normalTexture.name).toBe("bump");
expect(fsExtra.readFile.calls.count()).toBe(3); expect(fsExtra.readFile.calls.count()).toBe(3);
}); });
it('loads texture outside of the mtl directory', async () => { it("loads texture outside of the mtl directory", async () => {
const materials = await loadMtl(externalMaterialPath, options); const materials = await loadMtl(externalMaterialPath, options);
const material = materials[0]; const material = materials[0];
const baseColorTexture = material.pbrMetallicRoughness.baseColorTexture; const baseColorTexture = material.pbrMetallicRoughness.baseColorTexture;
expect(baseColorTexture.source).toBeDefined(); expect(baseColorTexture.source).toBeDefined();
expect(baseColorTexture.name).toBe('cesium'); expect(baseColorTexture.name).toBe("cesium");
}); });
it('does not load texture outside of the mtl directory when secure is true', async () => { it("does not load texture outside of the mtl directory when secure is true", async () => {
const spy = jasmine.createSpy('logger'); const spy = jasmine.createSpy("logger");
options.logger = spy; options.logger = spy;
options.secure = true; options.secure = true;
@ -169,30 +192,38 @@ describe('loadMtl', () => {
const material = materials[0]; const material = materials[0];
const baseColorTexture = material.pbrMetallicRoughness.baseColorTexture; const baseColorTexture = material.pbrMetallicRoughness.baseColorTexture;
expect(baseColorTexture).toBeUndefined(); expect(baseColorTexture).toBeUndefined();
expect(spy.calls.argsFor(0)[0].indexOf('Texture file is outside of the mtl directory and the secure flag is true. Attempting to read the texture file from within the obj directory instead') >= 0).toBe(true); expect(
expect(spy.calls.argsFor(1)[0].indexOf('ENOENT') >= 0).toBe(true); spy.calls
expect(spy.calls.argsFor(2)[0].indexOf('Could not read texture file') >= 0).toBe(true); .argsFor(0)[0]
.indexOf(
"Texture file is outside of the mtl directory and the secure flag is true. Attempting to read the texture file from within the obj directory instead"
) >= 0
).toBe(true);
expect(spy.calls.argsFor(1)[0].indexOf("ENOENT") >= 0).toBe(true);
expect(
spy.calls.argsFor(2)[0].indexOf("Could not read texture file") >= 0
).toBe(true);
}); });
it('loads textures from root directory when the texture paths do not exist', async () => { it("loads textures from root directory when the texture paths do not exist", async () => {
const materials = await loadMtl(resourcesInRootMaterialPath, options); const materials = await loadMtl(resourcesInRootMaterialPath, options);
const material = materials[0]; const material = materials[0];
const baseColorTexture = material.pbrMetallicRoughness.baseColorTexture; const baseColorTexture = material.pbrMetallicRoughness.baseColorTexture;
expect(baseColorTexture.source).toBeDefined(); expect(baseColorTexture.source).toBeDefined();
expect(baseColorTexture.name).toBe('cesium'); expect(baseColorTexture.name).toBe("cesium");
}); });
it('loads textures from root directory when texture is outside of the mtl directory and secure is true', async () => { it("loads textures from root directory when texture is outside of the mtl directory and secure is true", async () => {
options.secure = true; options.secure = true;
const materials = await loadMtl(externalInRootMaterialPath, options); const materials = await loadMtl(externalInRootMaterialPath, options);
const material = materials[0]; const material = materials[0];
const baseColorTexture = material.pbrMetallicRoughness.baseColorTexture; const baseColorTexture = material.pbrMetallicRoughness.baseColorTexture;
expect(baseColorTexture.source).toBeDefined(); expect(baseColorTexture.source).toBeDefined();
expect(baseColorTexture.name).toBe('cesium'); expect(baseColorTexture.name).toBe("cesium");
}); });
it('alpha of 0.0 is treated as 1.0', async () => { it("alpha of 0.0 is treated as 1.0", async () => {
const materials = await loadMtl(transparentMaterialPath, options); const materials = await loadMtl(transparentMaterialPath, options);
expect(materials.length).toBe(1); expect(materials.length).toBe(1);
const material = materials[0]; const material = materials[0];
@ -200,11 +231,11 @@ describe('loadMtl', () => {
expect(pbr.baseColorTexture).toBeUndefined(); expect(pbr.baseColorTexture).toBeUndefined();
expect(pbr.metallicRoughnessTexture).toBeUndefined(); expect(pbr.metallicRoughnessTexture).toBeUndefined();
expect(pbr.baseColorFactor[3]).toEqual(1.0); expect(pbr.baseColorFactor[3]).toEqual(1.0);
expect(material.alphaMode).toBe('OPAQUE'); expect(material.alphaMode).toBe("OPAQUE");
expect(material.doubleSided).toBe(false); expect(material.doubleSided).toBe(false);
}); });
it('ambient texture is ignored if it is the same as the diffuse texture', async () => { it("ambient texture is ignored if it is the same as the diffuse texture", async () => {
const materials = await loadMtl(sharedTexturesMaterialPath, options); const materials = await loadMtl(sharedTexturesMaterialPath, options);
expect(materials.length).toBe(1); expect(materials.length).toBe(1);
const material = materials[0]; const material = materials[0];
@ -213,7 +244,7 @@ describe('loadMtl', () => {
expect(pbr.occlusionTexture).toBeUndefined(); expect(pbr.occlusionTexture).toBeUndefined();
}); });
it('texture referenced by specular is decoded', async () => { it("texture referenced by specular is decoded", async () => {
const materials = await loadMtl(sharedTexturesMaterialPath, options); const materials = await loadMtl(sharedTexturesMaterialPath, options);
expect(materials.length).toBe(1); expect(materials.length).toBe(1);
const material = materials[0]; const material = materials[0];
@ -224,7 +255,7 @@ describe('loadMtl', () => {
expect(pbr.metallicRoughnessTexture.source).toBeUndefined(); expect(pbr.metallicRoughnessTexture.source).toBeUndefined();
}); });
it('texture referenced by diffuse and emissive is not decoded', async () => { it("texture referenced by diffuse and emissive is not decoded", async () => {
const materials = await loadMtl(sharedTexturesMaterial2Path, options); const materials = await loadMtl(sharedTexturesMaterial2Path, options);
expect(materials.length).toBe(1); expect(materials.length).toBe(1);
const material = materials[0]; const material = materials[0];
@ -234,8 +265,8 @@ describe('loadMtl', () => {
expect(pbr.baseColorTexture.source).toBeDefined(); expect(pbr.baseColorTexture.source).toBeDefined();
}); });
describe('metallicRoughness', () => { describe("metallicRoughness", () => {
it('creates default material', () => { it("creates default material", () => {
const material = loadMtl._createMaterial(undefined, options); const material = loadMtl._createMaterial(undefined, options);
const pbr = material.pbrMetallicRoughness; const pbr = material.pbrMetallicRoughness;
expect(pbr.baseColorTexture).toBeUndefined(); expect(pbr.baseColorTexture).toBeUndefined();
@ -247,21 +278,24 @@ describe('loadMtl', () => {
expect(material.normalTexture).toBeUndefined(); expect(material.normalTexture).toBeUndefined();
expect(material.ambientTexture).toBeUndefined(); expect(material.ambientTexture).toBeUndefined();
expect(material.emissiveFactor).toEqual([0.0, 0.0, 0.0]); expect(material.emissiveFactor).toEqual([0.0, 0.0, 0.0]);
expect(material.alphaMode).toBe('OPAQUE'); expect(material.alphaMode).toBe("OPAQUE");
expect(material.doubleSided).toBe(false); expect(material.doubleSided).toBe(false);
}); });
it('creates material with textures', () => { it("creates material with textures", () => {
options.metallicRoughness = true; options.metallicRoughness = true;
const material = loadMtl._createMaterial({ const material = loadMtl._createMaterial(
{
diffuseTexture: diffuseTexture, diffuseTexture: diffuseTexture,
ambientTexture: ambientTexture, ambientTexture: ambientTexture,
normalTexture: normalTexture, normalTexture: normalTexture,
emissiveTexture: emissiveTexture, emissiveTexture: emissiveTexture,
specularTexture: specularTexture, specularTexture: specularTexture,
specularShininessTexture : specularShininessTexture specularShininessTexture: specularShininessTexture,
}, options); },
options
);
const pbr = material.pbrMetallicRoughness; const pbr = material.pbrMetallicRoughness;
expect(pbr.baseColorTexture).toBeDefined(); expect(pbr.baseColorTexture).toBeDefined();
@ -273,57 +307,69 @@ describe('loadMtl', () => {
expect(material.normalTexture).toBeDefined(); expect(material.normalTexture).toBeDefined();
expect(material.occlusionTexture).toBeDefined(); expect(material.occlusionTexture).toBeDefined();
expect(material.emissiveFactor).toEqual([1.0, 1.0, 1.0]); expect(material.emissiveFactor).toEqual([1.0, 1.0, 1.0]);
expect(material.alphaMode).toBe('OPAQUE'); expect(material.alphaMode).toBe("OPAQUE");
expect(material.doubleSided).toBe(false); expect(material.doubleSided).toBe(false);
}); });
it('packs occlusion in metallic roughness texture', () => { it("packs occlusion in metallic roughness texture", () => {
options.metallicRoughness = true; options.metallicRoughness = true;
options.packOcclusion = true; options.packOcclusion = true;
const material = loadMtl._createMaterial({ const material = loadMtl._createMaterial(
{
ambientTexture: alphaTexture, ambientTexture: alphaTexture,
specularTexture: specularTexture, specularTexture: specularTexture,
specularShininessTexture : specularShininessTexture specularShininessTexture: specularShininessTexture,
}, options); },
options
);
const pbr = material.pbrMetallicRoughness; const pbr = material.pbrMetallicRoughness;
expect(pbr.metallicRoughnessTexture).toBeDefined(); expect(pbr.metallicRoughnessTexture).toBeDefined();
expect(pbr.metallicRoughnessTexture).toBe(material.occlusionTexture); expect(pbr.metallicRoughnessTexture).toBe(material.occlusionTexture);
}); });
it('does not create metallic roughness texture if decoded texture data is not available', () => { it("does not create metallic roughness texture if decoded texture data is not available", () => {
options.metallicRoughness = true; options.metallicRoughness = true;
options.packOcclusion = true; options.packOcclusion = true;
const material = loadMtl._createMaterial({ const material = loadMtl._createMaterial(
{
ambientTexture: ambientTexture, // Is a .gif which can't be decoded ambientTexture: ambientTexture, // Is a .gif which can't be decoded
specularTexture: specularTexture, specularTexture: specularTexture,
specularShininessTexture : specularShininessTexture specularShininessTexture: specularShininessTexture,
}, options); },
options
);
const pbr = material.pbrMetallicRoughness; const pbr = material.pbrMetallicRoughness;
expect(pbr.metallicRoughnessTexture).toBeUndefined(); expect(pbr.metallicRoughnessTexture).toBeUndefined();
expect(material.occlusionTexture).toBeUndefined(); expect(material.occlusionTexture).toBeUndefined();
}); });
it('sets material for transparent diffuse texture', () => { it("sets material for transparent diffuse texture", () => {
options.metallicRoughness = true; options.metallicRoughness = true;
const material = loadMtl._createMaterial({ const material = loadMtl._createMaterial(
diffuseTexture : transparentDiffuseTexture {
}, options); diffuseTexture: transparentDiffuseTexture,
expect(material.alphaMode).toBe('BLEND'); },
options
);
expect(material.alphaMode).toBe("BLEND");
expect(material.doubleSided).toBe(true); expect(material.doubleSided).toBe(true);
}); });
it('packs alpha texture in base color texture', () => { it("packs alpha texture in base color texture", () => {
options.metallicRoughness = true; options.metallicRoughness = true;
const material = loadMtl._createMaterial({ const material = loadMtl._createMaterial(
{
diffuseTexture: diffuseTexture, diffuseTexture: diffuseTexture,
alphaTexture : alphaTexture alphaTexture: alphaTexture,
}, options); },
options
);
const pbr = material.pbrMetallicRoughness; const pbr = material.pbrMetallicRoughness;
expect(pbr.baseColorTexture).toBeDefined(); expect(pbr.baseColorTexture).toBeDefined();
@ -334,33 +380,36 @@ describe('loadMtl', () => {
const pixelsLength = pixels.length / 4; const pixelsLength = pixels.length / 4;
for (let i = 0; i < pixelsLength; ++i) { for (let i = 0; i < pixelsLength; ++i) {
const alpha = pixels[i * 4 + 3]; const alpha = pixels[i * 4 + 3];
hasBlack = hasBlack || (alpha === 0); hasBlack = hasBlack || alpha === 0;
hasWhite = hasWhite || (alpha === 255); hasWhite = hasWhite || alpha === 255;
} }
expect(hasBlack).toBe(true); expect(hasBlack).toBe(true);
expect(hasWhite).toBe(true); expect(hasWhite).toBe(true);
expect(pbr.baseColorFactor[3]).toEqual(1); expect(pbr.baseColorFactor[3]).toEqual(1);
expect(material.alphaMode).toBe('BLEND'); expect(material.alphaMode).toBe("BLEND");
expect(material.doubleSided).toBe(true); expect(material.doubleSided).toBe(true);
}); });
it('uses diffuse texture if diffuse and alpha are the same', () => { it("uses diffuse texture if diffuse and alpha are the same", () => {
options.metallicRoughness = true; options.metallicRoughness = true;
const material = loadMtl._createMaterial({ const material = loadMtl._createMaterial(
{
diffuseTexture: diffuseTexture, diffuseTexture: diffuseTexture,
alphaTexture : diffuseTexture alphaTexture: diffuseTexture,
}, options); },
options
);
const pbr = material.pbrMetallicRoughness; const pbr = material.pbrMetallicRoughness;
expect(pbr.baseColorTexture).toBe(diffuseTexture); expect(pbr.baseColorTexture).toBe(diffuseTexture);
expect(material.alphaMode).toBe('BLEND'); expect(material.alphaMode).toBe("BLEND");
expect(material.doubleSided).toBe(true); expect(material.doubleSided).toBe(true);
}); });
}); });
describe('specularGlossiness', () => { describe("specularGlossiness", () => {
it('creates default material', () => { it("creates default material", () => {
options.specularGlossiness = true; options.specularGlossiness = true;
const material = loadMtl._createMaterial(undefined, options); const material = loadMtl._createMaterial(undefined, options);
const pbr = material.extensions.KHR_materials_pbrSpecularGlossiness; const pbr = material.extensions.KHR_materials_pbrSpecularGlossiness;
@ -373,21 +422,24 @@ describe('loadMtl', () => {
expect(material.normalTexture).toBeUndefined(); expect(material.normalTexture).toBeUndefined();
expect(material.occlusionTexture).toBeUndefined(); expect(material.occlusionTexture).toBeUndefined();
expect(material.emissiveFactor).toEqual([0.0, 0.0, 0.0]); expect(material.emissiveFactor).toEqual([0.0, 0.0, 0.0]);
expect(material.alphaMode).toBe('OPAQUE'); expect(material.alphaMode).toBe("OPAQUE");
expect(material.doubleSided).toBe(false); expect(material.doubleSided).toBe(false);
}); });
it('creates material with textures', () => { it("creates material with textures", () => {
options.specularGlossiness = true; options.specularGlossiness = true;
const material = loadMtl._createMaterial({ const material = loadMtl._createMaterial(
{
diffuseTexture: diffuseTexture, diffuseTexture: diffuseTexture,
ambientTexture: ambientTexture, ambientTexture: ambientTexture,
normalTexture: normalTexture, normalTexture: normalTexture,
emissiveTexture: emissiveTexture, emissiveTexture: emissiveTexture,
specularTexture: specularTexture, specularTexture: specularTexture,
specularShininessTexture : specularShininessTexture specularShininessTexture: specularShininessTexture,
}, options); },
options
);
const pbr = material.extensions.KHR_materials_pbrSpecularGlossiness; const pbr = material.extensions.KHR_materials_pbrSpecularGlossiness;
expect(pbr.diffuseTexture).toBeDefined(); expect(pbr.diffuseTexture).toBeDefined();
@ -399,40 +451,49 @@ describe('loadMtl', () => {
expect(material.normalTexture).toBeDefined(); expect(material.normalTexture).toBeDefined();
expect(material.occlusionTexture).toBeDefined(); expect(material.occlusionTexture).toBeDefined();
expect(material.emissiveFactor).toEqual([1.0, 1.0, 1.0]); expect(material.emissiveFactor).toEqual([1.0, 1.0, 1.0]);
expect(material.alphaMode).toBe('OPAQUE'); expect(material.alphaMode).toBe("OPAQUE");
expect(material.doubleSided).toBe(false); expect(material.doubleSided).toBe(false);
}); });
it('does not create specular glossiness texture if decoded texture data is not available', () => { it("does not create specular glossiness texture if decoded texture data is not available", () => {
options.specularGlossiness = true; options.specularGlossiness = true;
const material = loadMtl._createMaterial({ const material = loadMtl._createMaterial(
{
specularTexture: ambientTexture, // Is a .gif which can't be decoded specularTexture: ambientTexture, // Is a .gif which can't be decoded
specularShininessTexture : specularShininessTexture specularShininessTexture: specularShininessTexture,
}, options); },
options
);
const pbr = material.extensions.KHR_materials_pbrSpecularGlossiness; const pbr = material.extensions.KHR_materials_pbrSpecularGlossiness;
expect(pbr.specularGlossinessTexture).toBeUndefined(); expect(pbr.specularGlossinessTexture).toBeUndefined();
}); });
it('sets material for transparent diffuse texture', () => { it("sets material for transparent diffuse texture", () => {
options.specularGlossiness = true; options.specularGlossiness = true;
const material = loadMtl._createMaterial({ const material = loadMtl._createMaterial(
diffuseTexture : transparentDiffuseTexture {
}, options); diffuseTexture: transparentDiffuseTexture,
},
options
);
expect(material.alphaMode).toBe('BLEND'); expect(material.alphaMode).toBe("BLEND");
expect(material.doubleSided).toBe(true); expect(material.doubleSided).toBe(true);
}); });
it('packs alpha texture in diffuse texture', () => { it("packs alpha texture in diffuse texture", () => {
options.specularGlossiness = true; options.specularGlossiness = true;
const material = loadMtl._createMaterial({ const material = loadMtl._createMaterial(
{
diffuseTexture: diffuseTexture, diffuseTexture: diffuseTexture,
alphaTexture : alphaTexture alphaTexture: alphaTexture,
}, options); },
options
);
const pbr = material.extensions.KHR_materials_pbrSpecularGlossiness; const pbr = material.extensions.KHR_materials_pbrSpecularGlossiness;
expect(pbr.diffuseTexture).toBeDefined(); expect(pbr.diffuseTexture).toBeDefined();
@ -443,27 +504,30 @@ describe('loadMtl', () => {
const pixelsLength = pixels.length / 4; const pixelsLength = pixels.length / 4;
for (let i = 0; i < pixelsLength; ++i) { for (let i = 0; i < pixelsLength; ++i) {
const alpha = pixels[i * 4 + 3]; const alpha = pixels[i * 4 + 3];
hasBlack = hasBlack || (alpha === 0); hasBlack = hasBlack || alpha === 0;
hasWhite = hasWhite || (alpha === 255); hasWhite = hasWhite || alpha === 255;
} }
expect(hasBlack).toBe(true); expect(hasBlack).toBe(true);
expect(hasWhite).toBe(true); expect(hasWhite).toBe(true);
expect(pbr.diffuseFactor[3]).toEqual(1); expect(pbr.diffuseFactor[3]).toEqual(1);
expect(material.alphaMode).toBe('BLEND'); expect(material.alphaMode).toBe("BLEND");
expect(material.doubleSided).toBe(true); expect(material.doubleSided).toBe(true);
}); });
it('uses diffuse texture if diffuse and alpha are the same', () => { it("uses diffuse texture if diffuse and alpha are the same", () => {
options.specularGlossiness = true; options.specularGlossiness = true;
const material = loadMtl._createMaterial({ const material = loadMtl._createMaterial(
{
diffuseTexture: diffuseTexture, diffuseTexture: diffuseTexture,
alphaTexture : diffuseTexture alphaTexture: diffuseTexture,
}, options); },
options
);
const pbr = material.extensions.KHR_materials_pbrSpecularGlossiness; const pbr = material.extensions.KHR_materials_pbrSpecularGlossiness;
expect(pbr.diffuseTexture).toEqual(diffuseTexture); expect(pbr.diffuseTexture).toEqual(diffuseTexture);
expect(material.alphaMode).toBe('BLEND'); expect(material.alphaMode).toBe("BLEND");
expect(material.doubleSided).toBe(true); expect(material.doubleSided).toBe(true);
}); });
}); });

View File

@ -1,53 +1,72 @@
'use strict'; "use strict";
const Cesium = require('cesium'); const Cesium = require("cesium");
const path = require('path'); const path = require("path");
const loadObj = require('../../lib/loadObj'); const loadObj = require("../../lib/loadObj");
const obj2gltf = require('../../lib/obj2gltf'); const obj2gltf = require("../../lib/obj2gltf");
const Cartesian3 = Cesium.Cartesian3; const Cartesian3 = Cesium.Cartesian3;
const CesiumMath = Cesium.Math; const CesiumMath = Cesium.Math;
const clone = Cesium.clone; const clone = Cesium.clone;
const RuntimeError = Cesium.RuntimeError; const RuntimeError = Cesium.RuntimeError;
const objPath = 'specs/data/box/box.obj'; const objPath = "specs/data/box/box.obj";
const objRotatedUrl = 'specs/data/box-rotated/box-rotated.obj'; const objRotatedUrl = "specs/data/box-rotated/box-rotated.obj";
const objNormalsPath = 'specs/data/box-normals/box-normals.obj'; const objNormalsPath = "specs/data/box-normals/box-normals.obj";
const objUvsPath = 'specs/data/box-uvs/box-uvs.obj'; const objUvsPath = "specs/data/box-uvs/box-uvs.obj";
const objPositionsOnlyPath = 'specs/data/box-positions-only/box-positions-only.obj'; const objPositionsOnlyPath =
const objNegativeIndicesPath = 'specs/data/box-negative-indices/box-negative-indices.obj'; "specs/data/box-positions-only/box-positions-only.obj";
const objTrianglesPath = 'specs/data/box-triangles/box-triangles.obj'; const objNegativeIndicesPath =
const objObjectsPath = 'specs/data/box-objects/box-objects.obj'; "specs/data/box-negative-indices/box-negative-indices.obj";
const objGroupsPath = 'specs/data/box-groups/box-groups.obj'; const objTrianglesPath = "specs/data/box-triangles/box-triangles.obj";
const objObjectsGroupsPath = 'specs/data/box-objects-groups/box-objects-groups.obj'; const objObjectsPath = "specs/data/box-objects/box-objects.obj";
const objObjectsGroupsMaterialsPath = 'specs/data/box-objects-groups-materials/box-objects-groups-materials.obj'; const objGroupsPath = "specs/data/box-groups/box-groups.obj";
const objObjectsGroupsMaterialsPath2 = 'specs/data/box-objects-groups-materials-2/box-objects-groups-materials-2.obj'; const objObjectsGroupsPath =
const objUsemtlPath = 'specs/data/box-usemtl/box-usemtl.obj'; "specs/data/box-objects-groups/box-objects-groups.obj";
const objNoMaterialsPath = 'specs/data/box-no-materials/box-no-materials.obj'; const objObjectsGroupsMaterialsPath =
const objMultipleMaterialsPath = 'specs/data/box-multiple-materials/box-multiple-materials.obj'; "specs/data/box-objects-groups-materials/box-objects-groups-materials.obj";
const objUncleanedPath = 'specs/data/box-uncleaned/box-uncleaned.obj'; const objObjectsGroupsMaterialsPath2 =
const objMtllibPath = 'specs/data/box-mtllib/box-mtllib.obj'; "specs/data/box-objects-groups-materials-2/box-objects-groups-materials-2.obj";
const objMtllibSpacesPath = 'specs/data/box-mtllib-spaces/box mtllib.obj'; const objUsemtlPath = "specs/data/box-usemtl/box-usemtl.obj";
const objMissingMtllibPath = 'specs/data/box-missing-mtllib/box-missing-mtllib.obj'; const objNoMaterialsPath = "specs/data/box-no-materials/box-no-materials.obj";
const objMissingUsemtlPath = 'specs/data/box-missing-usemtl/box-missing-usemtl.obj'; const objMultipleMaterialsPath =
const objUnnamedMaterialPath = 'specs/data/box-unnamed-material/box-unnamed-material.obj'; "specs/data/box-multiple-materials/box-multiple-materials.obj";
const objExternalResourcesPath = 'specs/data/box-external-resources/box-external-resources.obj'; const objUncleanedPath = "specs/data/box-uncleaned/box-uncleaned.obj";
const objResourcesInRootPath = 'specs/data/box-resources-in-root/box-resources-in-root.obj'; const objMtllibPath = "specs/data/box-mtllib/box-mtllib.obj";
const objExternalResourcesInRootPath = 'specs/data/box-external-resources-in-root/box-external-resources-in-root.obj'; const objMtllibSpacesPath = "specs/data/box-mtllib-spaces/box mtllib.obj";
const objTexturedPath = 'specs/data/box-textured/box-textured.obj'; const objMissingMtllibPath =
const objMissingTexturePath = 'specs/data/box-missing-texture/box-missing-texture.obj'; "specs/data/box-missing-mtllib/box-missing-mtllib.obj";
const objSubdirectoriesPath = 'specs/data/box-subdirectories/box-textured.obj'; const objMissingUsemtlPath =
const objWindowsPaths = 'specs/data/box-windows-paths/box-windows-paths.obj'; "specs/data/box-missing-usemtl/box-missing-usemtl.obj";
const objInvalidContentsPath = 'specs/data/box/box.mtl'; const objUnnamedMaterialPath =
const objConcavePath = 'specs/data/concave/concave.obj'; "specs/data/box-unnamed-material/box-unnamed-material.obj";
const objUnnormalizedPath = 'specs/data/box-unnormalized/box-unnormalized.obj'; const objExternalResourcesPath =
const objMixedAttributesPath = 'specs/data/box-mixed-attributes/box-mixed-attributes.obj'; "specs/data/box-external-resources/box-external-resources.obj";
const objMissingAttributesPath = 'specs/data/box-missing-attributes/box-missing-attributes.obj'; const objResourcesInRootPath =
const objIncompletePositionsPath = 'specs/data/box-incomplete-attributes/box-incomplete-positions.obj'; "specs/data/box-resources-in-root/box-resources-in-root.obj";
const objIncompleteNormalsPath = 'specs/data/box-incomplete-attributes/box-incomplete-normals.obj'; const objExternalResourcesInRootPath =
const objIncompleteUvsPath = 'specs/data/box-incomplete-attributes/box-incomplete-uvs.obj'; "specs/data/box-external-resources-in-root/box-external-resources-in-root.obj";
const objIncorrectWindingOrderPath = 'specs/data/box-incorrect-winding-order/box-incorrect-winding-order.obj'; const objTexturedPath = "specs/data/box-textured/box-textured.obj";
const objInvalidPath = 'invalid.obj'; const objMissingTexturePath =
"specs/data/box-missing-texture/box-missing-texture.obj";
const objSubdirectoriesPath = "specs/data/box-subdirectories/box-textured.obj";
const objWindowsPaths = "specs/data/box-windows-paths/box-windows-paths.obj";
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 objIncorrectWindingOrderPath =
"specs/data/box-incorrect-winding-order/box-incorrect-winding-order.obj";
const objInvalidPath = "invalid.obj";
function getMeshes(data) { function getMeshes(data) {
let meshes = []; let meshes = [];
@ -75,14 +94,14 @@ function getPrimitives(data) {
let options; let options;
describe('loadObj', () => { describe("loadObj", () => {
beforeEach(() => { beforeEach(() => {
options = clone(obj2gltf.defaults); options = clone(obj2gltf.defaults);
options.overridingTextures = {}; options.overridingTextures = {};
options.logger = () => {}; options.logger = () => {};
}); });
it('loads obj with positions, normals, and uvs', async () => { it("loads obj with positions, normals, and uvs", async () => {
const data = await loadObj(objPath, options); const data = await loadObj(objPath, options);
const materials = data.materials; const materials = data.materials;
const nodes = data.nodes; const nodes = data.nodes;
@ -90,7 +109,7 @@ describe('loadObj', () => {
const meshes = getMeshes(data); const meshes = getMeshes(data);
const primitives = getPrimitives(data); const primitives = getPrimitives(data);
expect(name).toBe('box'); expect(name).toBe("box");
expect(materials.length).toBe(1); expect(materials.length).toBe(1);
expect(nodes.length).toBe(1); expect(nodes.length).toBe(1);
expect(meshes.length).toBe(1); expect(meshes.length).toBe(1);
@ -100,16 +119,16 @@ describe('loadObj', () => {
const mesh = meshes[0]; const mesh = meshes[0];
const primitive = primitives[0]; const primitive = primitives[0];
expect(node.name).toBe('Cube'); expect(node.name).toBe("Cube");
expect(mesh.name).toBe('Cube-Mesh'); expect(mesh.name).toBe("Cube-Mesh");
expect(primitive.positions.length / 3).toBe(24); expect(primitive.positions.length / 3).toBe(24);
expect(primitive.normals.length / 3).toBe(24); expect(primitive.normals.length / 3).toBe(24);
expect(primitive.uvs.length / 2).toBe(24); expect(primitive.uvs.length / 2).toBe(24);
expect(primitive.indices.length).toBe(36); expect(primitive.indices.length).toBe(36);
expect(primitive.material).toBe('Material'); expect(primitive.material).toBe("Material");
}); });
it('loads obj with normals', async () => { it("loads obj with normals", async () => {
const data = await loadObj(objNormalsPath, options); const data = await loadObj(objNormalsPath, options);
const primitive = getPrimitives(data)[0]; const primitive = getPrimitives(data)[0];
expect(primitive.positions.length / 3).toBe(24); expect(primitive.positions.length / 3).toBe(24);
@ -117,7 +136,7 @@ describe('loadObj', () => {
expect(primitive.uvs.length / 2).toBe(0); expect(primitive.uvs.length / 2).toBe(0);
}); });
it('normalizes normals', async () => { it("normalizes normals", async () => {
const data = await loadObj(objUnnormalizedPath, options); const data = await loadObj(objUnnormalizedPath, options);
const scratchNormal = new Cesium.Cartesian3(); const scratchNormal = new Cesium.Cartesian3();
const primitive = getPrimitives(data)[0]; const primitive = getPrimitives(data)[0];
@ -127,12 +146,23 @@ describe('loadObj', () => {
const normalX = normals.get(i * 3); const normalX = normals.get(i * 3);
const normalY = normals.get(i * 3 + 1); const normalY = normals.get(i * 3 + 1);
const normalZ = normals.get(i * 3 + 2); const normalZ = normals.get(i * 3 + 2);
const normal = Cartesian3.fromElements(normalX, normalY, normalZ, scratchNormal); const normal = Cartesian3.fromElements(
expect(CesiumMath.equalsEpsilon(Cartesian3.magnitude(normal), 1.0, CesiumMath.EPSILON5)).toBe(true); normalX,
normalY,
normalZ,
scratchNormal
);
expect(
CesiumMath.equalsEpsilon(
Cartesian3.magnitude(normal),
1.0,
CesiumMath.EPSILON5
)
).toBe(true);
} }
}); });
it('loads obj with uvs', async () => { it("loads obj with uvs", async () => {
const data = await loadObj(objUvsPath, options); const data = await loadObj(objUvsPath, options);
const primitive = getPrimitives(data)[0]; const primitive = getPrimitives(data)[0];
expect(primitive.positions.length / 3).toBe(20); expect(primitive.positions.length / 3).toBe(20);
@ -140,140 +170,142 @@ describe('loadObj', () => {
expect(primitive.uvs.length / 2).toBe(20); expect(primitive.uvs.length / 2).toBe(20);
}); });
it('loads obj with negative indices', async () => { it("loads obj with negative indices", async () => {
const results = [ const results = [
await loadObj(objPositionsOnlyPath, options), await loadObj(objPositionsOnlyPath, options),
await loadObj(objNegativeIndicesPath, options) await loadObj(objNegativeIndicesPath, options),
]; ];
const positionsReference = getPrimitives(results[0])[0].positions.toFloatBuffer(); const positionsReference = getPrimitives(
results[0]
)[0].positions.toFloatBuffer();
const positions = getPrimitives(results[1])[0].positions.toFloatBuffer(); const positions = getPrimitives(results[1])[0].positions.toFloatBuffer();
expect(positions).toEqual(positionsReference); expect(positions).toEqual(positionsReference);
}); });
it('loads obj with triangle faces', async () => { it("loads obj with triangle faces", async () => {
const data = await loadObj(objTrianglesPath, options); const data = await loadObj(objTrianglesPath, options);
const primitive = getPrimitives(data)[0]; const primitive = getPrimitives(data)[0];
expect(primitive.positions.length / 3).toBe(24); expect(primitive.positions.length / 3).toBe(24);
expect(primitive.indices.length).toBe(36); expect(primitive.indices.length).toBe(36);
}); });
it('loads obj with objects', async () => { it("loads obj with objects", async () => {
const data = await loadObj(objObjectsPath, options); const data = await loadObj(objObjectsPath, options);
const nodes = data.nodes; const nodes = data.nodes;
expect(nodes.length).toBe(3); expect(nodes.length).toBe(3);
expect(nodes[0].name).toBe('CubeBlue'); expect(nodes[0].name).toBe("CubeBlue");
expect(nodes[1].name).toBe('CubeGreen'); expect(nodes[1].name).toBe("CubeGreen");
expect(nodes[2].name).toBe('CubeRed'); expect(nodes[2].name).toBe("CubeRed");
const primitives = getPrimitives(data); const primitives = getPrimitives(data);
expect(primitives.length).toBe(3); expect(primitives.length).toBe(3);
expect(primitives[0].material).toBe('Blue'); expect(primitives[0].material).toBe("Blue");
expect(primitives[1].material).toBe('Green'); expect(primitives[1].material).toBe("Green");
expect(primitives[2].material).toBe('Red'); expect(primitives[2].material).toBe("Red");
}); });
it('loads obj with groups', async () => { it("loads obj with groups", async () => {
const data = await loadObj(objGroupsPath, options); const data = await loadObj(objGroupsPath, options);
const nodes = data.nodes; const nodes = data.nodes;
expect(nodes.length).toBe(3); expect(nodes.length).toBe(3);
expect(nodes[0].name).toBe('CubeBlue'); expect(nodes[0].name).toBe("CubeBlue");
expect(nodes[1].name).toBe('CubeGreen'); expect(nodes[1].name).toBe("CubeGreen");
expect(nodes[2].name).toBe('CubeRed'); expect(nodes[2].name).toBe("CubeRed");
const primitives = getPrimitives(data); const primitives = getPrimitives(data);
expect(primitives.length).toBe(3); expect(primitives.length).toBe(3);
expect(primitives[0].material).toBe('Blue'); expect(primitives[0].material).toBe("Blue");
expect(primitives[1].material).toBe('Green'); expect(primitives[1].material).toBe("Green");
expect(primitives[2].material).toBe('Red'); expect(primitives[2].material).toBe("Red");
}); });
it('loads obj with objects and groups', async () => { it("loads obj with objects and groups", async () => {
const data = await loadObj(objObjectsGroupsPath, options); const data = await loadObj(objObjectsGroupsPath, options);
const nodes = data.nodes; const nodes = data.nodes;
expect(nodes.length).toBe(3); expect(nodes.length).toBe(3);
expect(nodes[0].name).toBe('CubeBlue'); expect(nodes[0].name).toBe("CubeBlue");
expect(nodes[1].name).toBe('CubeGreen'); expect(nodes[1].name).toBe("CubeGreen");
expect(nodes[2].name).toBe('CubeRed'); expect(nodes[2].name).toBe("CubeRed");
const meshes = getMeshes(data); const meshes = getMeshes(data);
expect(meshes.length).toBe(3); expect(meshes.length).toBe(3);
expect(meshes[0].name).toBe('CubeBlue_CubeBlue_Blue'); expect(meshes[0].name).toBe("CubeBlue_CubeBlue_Blue");
expect(meshes[1].name).toBe('CubeGreen_CubeGreen_Green'); expect(meshes[1].name).toBe("CubeGreen_CubeGreen_Green");
expect(meshes[2].name).toBe('CubeRed_CubeRed_Red'); expect(meshes[2].name).toBe("CubeRed_CubeRed_Red");
const primitives = getPrimitives(data); const primitives = getPrimitives(data);
expect(primitives.length).toBe(3); expect(primitives.length).toBe(3);
expect(primitives[0].material).toBe('Blue'); expect(primitives[0].material).toBe("Blue");
expect(primitives[1].material).toBe('Green'); expect(primitives[1].material).toBe("Green");
expect(primitives[2].material).toBe('Red'); expect(primitives[2].material).toBe("Red");
}); });
function loadsObjWithObjectsGroupsAndMaterials(data) { function loadsObjWithObjectsGroupsAndMaterials(data) {
const nodes = data.nodes; const nodes = data.nodes;
expect(nodes.length).toBe(1); expect(nodes.length).toBe(1);
expect(nodes[0].name).toBe('Cube'); expect(nodes[0].name).toBe("Cube");
const meshes = getMeshes(data); const meshes = getMeshes(data);
expect(meshes.length).toBe(3); expect(meshes.length).toBe(3);
expect(meshes[0].name).toBe('Blue'); expect(meshes[0].name).toBe("Blue");
expect(meshes[1].name).toBe('Green'); expect(meshes[1].name).toBe("Green");
expect(meshes[2].name).toBe('Red'); expect(meshes[2].name).toBe("Red");
const primitives = getPrimitives(data); const primitives = getPrimitives(data);
expect(primitives.length).toBe(6); expect(primitives.length).toBe(6);
expect(primitives[0].material).toBe('Blue'); expect(primitives[0].material).toBe("Blue");
expect(primitives[1].material).toBe('Green'); expect(primitives[1].material).toBe("Green");
expect(primitives[2].material).toBe('Green'); expect(primitives[2].material).toBe("Green");
expect(primitives[3].material).toBe('Red'); expect(primitives[3].material).toBe("Red");
expect(primitives[4].material).toBe('Red'); expect(primitives[4].material).toBe("Red");
expect(primitives[5].material).toBe('Blue'); expect(primitives[5].material).toBe("Blue");
} }
it('loads obj with objects, groups, and materials', async () => { it("loads obj with objects, groups, and materials", async () => {
const data = await loadObj(objObjectsGroupsMaterialsPath, options); const data = await loadObj(objObjectsGroupsMaterialsPath, options);
loadsObjWithObjectsGroupsAndMaterials(data); loadsObjWithObjectsGroupsAndMaterials(data);
}); });
it('loads obj with objects, groups, and materials (2)', async () => { it("loads obj with objects, groups, and materials (2)", async () => {
// The usemtl lines are placed in an unordered fashion but // The usemtl lines are placed in an unordered fashion but
// should produce the same result as the previous test // should produce the same result as the previous test
const data = await loadObj(objObjectsGroupsMaterialsPath2, options); const data = await loadObj(objObjectsGroupsMaterialsPath2, options);
loadsObjWithObjectsGroupsAndMaterials(data); loadsObjWithObjectsGroupsAndMaterials(data);
}); });
it('loads obj with concave face containing 5 vertices', async () => { it("loads obj with concave face containing 5 vertices", async () => {
const data = await loadObj(objConcavePath, options); const data = await loadObj(objConcavePath, options);
const primitive = getPrimitives(data)[0]; const primitive = getPrimitives(data)[0];
expect(primitive.positions.length / 3).toBe(30); expect(primitive.positions.length / 3).toBe(30);
expect(primitive.indices.length).toBe(48); expect(primitive.indices.length).toBe(48);
}); });
it('loads obj with usemtl only', async () => { it("loads obj with usemtl only", async () => {
const data = await loadObj(objUsemtlPath, options); const data = await loadObj(objUsemtlPath, options);
const nodes = data.nodes; const nodes = data.nodes;
expect(nodes.length).toBe(1); expect(nodes.length).toBe(1);
expect(nodes[0].name).toBe('Node'); // default name expect(nodes[0].name).toBe("Node"); // default name
const meshes = getMeshes(data); const meshes = getMeshes(data);
expect(meshes.length).toBe(1); expect(meshes.length).toBe(1);
expect(meshes[0].name).toBe('Node-Mesh'); expect(meshes[0].name).toBe("Node-Mesh");
const primitives = getPrimitives(data); const primitives = getPrimitives(data);
expect(primitives.length).toBe(3); expect(primitives.length).toBe(3);
expect(primitives[0].material).toBe('Blue'); expect(primitives[0].material).toBe("Blue");
expect(primitives[1].material).toBe('Green'); expect(primitives[1].material).toBe("Green");
expect(primitives[2].material).toBe('Red'); expect(primitives[2].material).toBe("Red");
}); });
it('loads obj with no materials', async () => { it("loads obj with no materials", async () => {
const data = await loadObj(objNoMaterialsPath, options); const data = await loadObj(objNoMaterialsPath, options);
const nodes = data.nodes; const nodes = data.nodes;
expect(nodes.length).toBe(1); expect(nodes.length).toBe(1);
expect(nodes[0].name).toBe('Node'); // default name expect(nodes[0].name).toBe("Node"); // default name
const primitives = getPrimitives(data); const primitives = getPrimitives(data);
expect(primitives.length).toBe(1); expect(primitives.length).toBe(1);
}); });
it('loads obj with multiple materials', async () => { it("loads obj with multiple materials", async () => {
// The usemtl markers are interleaved, but should condense to just three primitives // The usemtl markers are interleaved, but should condense to just three primitives
const data = await loadObj(objMultipleMaterialsPath, options); const data = await loadObj(objMultipleMaterialsPath, options);
const nodes = data.nodes; const nodes = data.nodes;
@ -285,9 +317,9 @@ describe('loadObj', () => {
expect(primitives[0].indices.length).toBe(12); expect(primitives[0].indices.length).toBe(12);
expect(primitives[1].indices.length).toBe(12); expect(primitives[1].indices.length).toBe(12);
expect(primitives[2].indices.length).toBe(12); expect(primitives[2].indices.length).toBe(12);
expect(primitives[0].material).toBe('Red'); expect(primitives[0].material).toBe("Red");
expect(primitives[1].material).toBe('Green'); expect(primitives[1].material).toBe("Green");
expect(primitives[2].material).toBe('Blue'); expect(primitives[2].material).toBe("Blue");
for (let i = 0; i < 3; ++i) { for (let i = 0; i < 3; ++i) {
const indices = primitives[i].indices; const indices = primitives[i].indices;
@ -297,7 +329,7 @@ describe('loadObj', () => {
} }
}); });
it('loads obj uncleaned', async () => { it("loads obj uncleaned", async () => {
// Obj with extraneous o, g, and usemtl lines // Obj with extraneous o, g, and usemtl lines
// Also tests handling of o and g lines with the same names // Also tests handling of o and g lines with the same names
const data = await loadObj(objUncleanedPath, options); const data = await loadObj(objUncleanedPath, options);
@ -309,11 +341,11 @@ describe('loadObj', () => {
expect(meshes.length).toBe(1); expect(meshes.length).toBe(1);
expect(primitives.length).toBe(1); expect(primitives.length).toBe(1);
expect(nodes[0].name).toBe('Cube'); expect(nodes[0].name).toBe("Cube");
expect(meshes[0].name).toBe('Cube_1'); expect(meshes[0].name).toBe("Cube_1");
}); });
it('loads obj with multiple mtllibs', async () => { it("loads obj with multiple mtllibs", async () => {
const data = await loadObj(objMtllibPath, options); const data = await loadObj(objMtllibPath, options);
const materials = data.materials; const materials = data.materials;
expect(materials.length).toBe(3); expect(materials.length).toBe(3);
@ -323,15 +355,21 @@ describe('loadObj', () => {
return a.name.localeCompare(b.name); return a.name.localeCompare(b.name);
}); });
expect(materials[0].name).toBe('Blue'); expect(materials[0].name).toBe("Blue");
expect(materials[0].pbrMetallicRoughness.baseColorFactor).toEqual([0.0, 0.0, 0.64, 1.0]); expect(materials[0].pbrMetallicRoughness.baseColorFactor).toEqual([
expect(materials[1].name).toBe('Green'); 0.0, 0.0, 0.64, 1.0,
expect(materials[1].pbrMetallicRoughness.baseColorFactor).toEqual([0.0, 0.64, 0.0, 1.0]); ]);
expect(materials[2].name).toBe('Red'); expect(materials[1].name).toBe("Green");
expect(materials[2].pbrMetallicRoughness.baseColorFactor).toEqual([0.64, 0.0, 0.0, 1.0]); expect(materials[1].pbrMetallicRoughness.baseColorFactor).toEqual([
0.0, 0.64, 0.0, 1.0,
]);
expect(materials[2].name).toBe("Red");
expect(materials[2].pbrMetallicRoughness.baseColorFactor).toEqual([
0.64, 0.0, 0.0, 1.0,
]);
}); });
it('loads obj with mtllib paths with spaces', async () => { it("loads obj with mtllib paths with spaces", async () => {
const data = await loadObj(objMtllibSpacesPath, options); const data = await loadObj(objMtllibSpacesPath, options);
const materials = data.materials; const materials = data.materials;
expect(materials.length).toBe(3); expect(materials.length).toBe(3);
@ -341,71 +379,98 @@ describe('loadObj', () => {
return a.name.localeCompare(b.name); return a.name.localeCompare(b.name);
}); });
expect(materials[0].name).toBe('Blue'); expect(materials[0].name).toBe("Blue");
expect(materials[0].pbrMetallicRoughness.baseColorFactor).toEqual([0.0, 0.0, 0.64, 1.0]); expect(materials[0].pbrMetallicRoughness.baseColorFactor).toEqual([
expect(materials[1].name).toBe('Green'); 0.0, 0.0, 0.64, 1.0,
expect(materials[1].pbrMetallicRoughness.baseColorFactor).toEqual([0.0, 0.64, 0.0, 1.0]); ]);
expect(materials[2].name).toBe('Red'); expect(materials[1].name).toBe("Green");
expect(materials[2].pbrMetallicRoughness.baseColorFactor).toEqual([0.64, 0.0, 0.0, 1.0]); expect(materials[1].pbrMetallicRoughness.baseColorFactor).toEqual([
0.0, 0.64, 0.0, 1.0,
]);
expect(materials[2].name).toBe("Red");
expect(materials[2].pbrMetallicRoughness.baseColorFactor).toEqual([
0.64, 0.0, 0.0, 1.0,
]);
}); });
it('loads obj with missing mtllib', async () => { it("loads obj with missing mtllib", async () => {
const spy = jasmine.createSpy('logger'); const spy = jasmine.createSpy("logger");
options.logger = spy; options.logger = spy;
const data = await loadObj(objMissingMtllibPath, options); const data = await loadObj(objMissingMtllibPath, options);
expect(data.materials.length).toBe(0); expect(data.materials.length).toBe(0);
expect(spy.calls.argsFor(0)[0].indexOf('ENOENT') >= 0).toBe(true); expect(spy.calls.argsFor(0)[0].indexOf("ENOENT") >= 0).toBe(true);
expect(spy.calls.argsFor(0)[0].indexOf(path.resolve('/box.mtl')) >= 0).toBe(true); expect(spy.calls.argsFor(0)[0].indexOf(path.resolve("/box.mtl")) >= 0).toBe(
expect(spy.calls.argsFor(1)[0].indexOf('Attempting to read the material file from within the obj directory instead.') >= 0).toBe(true); true
expect(spy.calls.argsFor(2)[0].indexOf('ENOENT') >= 0).toBe(true); );
expect(spy.calls.argsFor(3)[0].indexOf('Could not read material file') >= 0).toBe(true); expect(
spy.calls
.argsFor(1)[0]
.indexOf(
"Attempting to read the material file from within the obj directory instead."
) >= 0
).toBe(true);
expect(spy.calls.argsFor(2)[0].indexOf("ENOENT") >= 0).toBe(true);
expect(
spy.calls.argsFor(3)[0].indexOf("Could not read material file") >= 0
).toBe(true);
}); });
it('loads obj with missing usemtl', async () => { it("loads obj with missing usemtl", async () => {
const data = await loadObj(objMissingUsemtlPath, options); const data = await loadObj(objMissingUsemtlPath, options);
expect(data.materials.length).toBe(1); expect(data.materials.length).toBe(1);
expect(data.nodes[0].meshes[0].primitives[0].material).toBe('Material'); expect(data.nodes[0].meshes[0].primitives[0].material).toBe("Material");
}); });
it('loads obj with unnamed material', async () => { it("loads obj with unnamed material", async () => {
const data = await loadObj(objUnnamedMaterialPath, options); const data = await loadObj(objUnnamedMaterialPath, options);
expect(data.materials.length).toBe(1); expect(data.materials.length).toBe(1);
expect(data.nodes[0].meshes[0].primitives[0].material).toBe(''); expect(data.nodes[0].meshes[0].primitives[0].material).toBe("");
}); });
it('loads .mtl outside of the obj directory', async () => { it("loads .mtl outside of the obj directory", async () => {
const data = await loadObj(objExternalResourcesPath, options); const data = await loadObj(objExternalResourcesPath, options);
const materials = data.materials; const materials = data.materials;
expect(materials.length).toBe(2); expect(materials.length).toBe(2);
// .mtl files are loaded in an arbitrary order, so find the "MaterialTextured" material // .mtl files are loaded in an arbitrary order, so find the "MaterialTextured" material
const materialTextured = materials[0].name === 'MaterialTextured' ? materials[0] : materials[1]; const materialTextured =
const baseColorTexture = materialTextured.pbrMetallicRoughness.baseColorTexture; materials[0].name === "MaterialTextured" ? materials[0] : materials[1];
const baseColorTexture =
materialTextured.pbrMetallicRoughness.baseColorTexture;
expect(baseColorTexture.source).toBeDefined(); expect(baseColorTexture.source).toBeDefined();
expect(baseColorTexture.name).toEqual('cesium'); expect(baseColorTexture.name).toEqual("cesium");
}); });
it('does not load .mtl outside of the obj directory when secure is true', async () => { it("does not load .mtl outside of the obj directory when secure is true", async () => {
const spy = jasmine.createSpy('logger'); const spy = jasmine.createSpy("logger");
options.logger = spy; options.logger = spy;
options.secure = true; options.secure = true;
const data = await loadObj(objExternalResourcesPath, options); const data = await loadObj(objExternalResourcesPath, options);
expect(data.materials.length).toBe(1); // obj references 2 materials, one of which is outside the input directory expect(data.materials.length).toBe(1); // obj references 2 materials, one of which is outside the input directory
expect(spy.calls.argsFor(0)[0].indexOf('The material file is outside of the obj directory and the secure flag is true. Attempting to read the material file from within the obj directory instead.') >= 0).toBe(true); expect(
expect(spy.calls.argsFor(1)[0].indexOf('ENOENT') >= 0).toBe(true); spy.calls
expect(spy.calls.argsFor(2)[0].indexOf('Could not read material file') >= 0).toBe(true); .argsFor(0)[0]
.indexOf(
"The material file is outside of the obj directory and the secure flag is true. Attempting to read the material file from within the obj directory instead."
) >= 0
).toBe(true);
expect(spy.calls.argsFor(1)[0].indexOf("ENOENT") >= 0).toBe(true);
expect(
spy.calls.argsFor(2)[0].indexOf("Could not read material file") >= 0
).toBe(true);
}); });
it('loads .mtl from root directory when the .mtl path does not exist', async () => { it("loads .mtl from root directory when the .mtl path does not exist", async () => {
const data = await loadObj(objResourcesInRootPath, options); const data = await loadObj(objResourcesInRootPath, options);
const baseColorTexture = data.materials[0].pbrMetallicRoughness.baseColorTexture; const baseColorTexture =
expect(baseColorTexture.name).toBe('cesium'); data.materials[0].pbrMetallicRoughness.baseColorTexture;
expect(baseColorTexture.name).toBe("cesium");
expect(baseColorTexture.source).toBeDefined(); expect(baseColorTexture.source).toBeDefined();
}); });
it('loads .mtl from root directory when the .mtl path is outside of the obj directory and secure is true', async () => { it("loads .mtl from root directory when the .mtl path is outside of the obj directory and secure is true", async () => {
options.secure = true; options.secure = true;
const data = await loadObj(objExternalResourcesInRootPath, options); const data = await loadObj(objExternalResourcesInRootPath, options);
@ -413,48 +478,64 @@ describe('loadObj', () => {
expect(materials.length).toBe(2); expect(materials.length).toBe(2);
// .mtl files are loaded in an arbitrary order, so find the "MaterialTextured" material // .mtl files are loaded in an arbitrary order, so find the "MaterialTextured" material
const materialTextured = materials[0].name === 'MaterialTextured' ? materials[0] : materials[1]; const materialTextured =
const baseColorTexture = materialTextured.pbrMetallicRoughness.baseColorTexture; materials[0].name === "MaterialTextured" ? materials[0] : materials[1];
const baseColorTexture =
materialTextured.pbrMetallicRoughness.baseColorTexture;
expect(baseColorTexture.source).toBeDefined(); expect(baseColorTexture.source).toBeDefined();
expect(baseColorTexture.name).toEqual('cesium'); expect(baseColorTexture.name).toEqual("cesium");
}); });
it('loads obj with texture', async () => { it("loads obj with texture", async () => {
const data = await loadObj(objTexturedPath, options); const data = await loadObj(objTexturedPath, options);
const baseColorTexture = data.materials[0].pbrMetallicRoughness.baseColorTexture; const baseColorTexture =
expect(baseColorTexture.name).toBe('cesium'); data.materials[0].pbrMetallicRoughness.baseColorTexture;
expect(baseColorTexture.name).toBe("cesium");
expect(baseColorTexture.source).toBeDefined(); expect(baseColorTexture.source).toBeDefined();
}); });
it('loads obj with missing texture', async () => { it("loads obj with missing texture", async () => {
const spy = jasmine.createSpy('logger'); const spy = jasmine.createSpy("logger");
options.logger = spy; options.logger = spy;
const data = await loadObj(objMissingTexturePath, options); const data = await loadObj(objMissingTexturePath, options);
const baseColorTexture = data.materials[0].pbrMetallicRoughness.baseColorTexture; const baseColorTexture =
data.materials[0].pbrMetallicRoughness.baseColorTexture;
expect(baseColorTexture).toBeUndefined(); expect(baseColorTexture).toBeUndefined();
expect(spy.calls.argsFor(0)[0].indexOf('ENOENT') >= 0).toBe(true); expect(spy.calls.argsFor(0)[0].indexOf("ENOENT") >= 0).toBe(true);
expect(spy.calls.argsFor(0)[0].indexOf(path.resolve('/cesium.png')) >= 0).toBe(true); expect(
expect(spy.calls.argsFor(1)[0].indexOf('Attempting to read the texture file from within the obj directory instead.') >= 0).toBe(true); spy.calls.argsFor(0)[0].indexOf(path.resolve("/cesium.png")) >= 0
expect(spy.calls.argsFor(2)[0].indexOf('ENOENT') >= 0).toBe(true); ).toBe(true);
expect(spy.calls.argsFor(3)[0].indexOf('Could not read texture file') >= 0).toBe(true); expect(
spy.calls
.argsFor(1)[0]
.indexOf(
"Attempting to read the texture file from within the obj directory instead."
) >= 0
).toBe(true);
expect(spy.calls.argsFor(2)[0].indexOf("ENOENT") >= 0).toBe(true);
expect(
spy.calls.argsFor(3)[0].indexOf("Could not read texture file") >= 0
).toBe(true);
}); });
it('loads obj with subdirectories', async () => { it("loads obj with subdirectories", async () => {
const data = await loadObj(objSubdirectoriesPath, options); const data = await loadObj(objSubdirectoriesPath, options);
const baseColorTexture = data.materials[0].pbrMetallicRoughness.baseColorTexture; const baseColorTexture =
expect(baseColorTexture.name).toBe('cesium'); data.materials[0].pbrMetallicRoughness.baseColorTexture;
expect(baseColorTexture.name).toBe("cesium");
expect(baseColorTexture.source).toBeDefined(); expect(baseColorTexture.source).toBeDefined();
}); });
it('loads obj with windows paths', async () => { it("loads obj with windows paths", async () => {
const data = await loadObj(objWindowsPaths, options); const data = await loadObj(objWindowsPaths, options);
const baseColorTexture = data.materials[0].pbrMetallicRoughness.baseColorTexture; const baseColorTexture =
expect(baseColorTexture.name).toBe('cesium'); data.materials[0].pbrMetallicRoughness.baseColorTexture;
expect(baseColorTexture.name).toBe("cesium");
expect(baseColorTexture.source).toBeDefined(); expect(baseColorTexture.source).toBeDefined();
}); });
it('separates faces that don\'t use the same attributes as other faces in the primitive', async () => { it("separates faces that don't use the same attributes as other faces in the primitive", async () => {
const data = await loadObj(objMixedAttributesPath, options); const data = await loadObj(objMixedAttributesPath, options);
const primitives = getPrimitives(data); const primitives = getPrimitives(data);
expect(primitives.length).toBe(4); expect(primitives.length).toBe(4);
@ -466,16 +547,29 @@ describe('loadObj', () => {
function getFirstPosition(data) { function getFirstPosition(data) {
const primitive = getPrimitives(data)[0]; const primitive = getPrimitives(data)[0];
return new Cartesian3(primitive.positions.get(0), primitive.positions.get(1), primitive.positions.get(2)); return new Cartesian3(
primitive.positions.get(0),
primitive.positions.get(1),
primitive.positions.get(2)
);
} }
function getFirstNormal(data) { function getFirstNormal(data) {
const primitive = getPrimitives(data)[0]; const primitive = getPrimitives(data)[0];
return new Cartesian3(primitive.normals.get(0), primitive.normals.get(1), primitive.normals.get(2)); return new Cartesian3(
primitive.normals.get(0),
primitive.normals.get(1),
primitive.normals.get(2)
);
} }
async function checkAxisConversion(inputUpAxis, outputUpAxis, position, normal) { async function checkAxisConversion(
const sameAxis = (inputUpAxis === outputUpAxis); inputUpAxis,
outputUpAxis,
position,
normal
) {
const sameAxis = inputUpAxis === outputUpAxis;
options.inputUpAxis = inputUpAxis; options.inputUpAxis = inputUpAxis;
options.outputUpAxis = outputUpAxis; options.outputUpAxis = outputUpAxis;
const data = await loadObj(objRotatedUrl, options); const data = await loadObj(objRotatedUrl, options);
@ -490,12 +584,12 @@ describe('loadObj', () => {
} }
} }
it('performs up axis conversion', async () => { it("performs up axis conversion", async () => {
const data = await loadObj(objRotatedUrl, options); const data = await loadObj(objRotatedUrl, options);
const position = getFirstPosition(data); const position = getFirstPosition(data);
const normal = getFirstNormal(data); const normal = getFirstNormal(data);
const axes = ['X', 'Y', 'Z']; const axes = ["X", "Y", "Z"];
const axesLength = axes.length; const axesLength = axes.length;
for (let i = 0; i < axesLength; ++i) { for (let i = 0; i < axesLength; ++i) {
for (let j = 0; j < axesLength; ++j) { for (let j = 0; j < axesLength; ++j) {
@ -504,7 +598,7 @@ describe('loadObj', () => {
} }
}); });
it('ignores missing normals and uvs', async () => { it("ignores missing normals and uvs", async () => {
const data = await loadObj(objMissingAttributesPath, options); const data = await loadObj(objMissingAttributesPath, options);
const primitive = getPrimitives(data)[0]; const primitive = getPrimitives(data)[0];
expect(primitive.positions.length).toBeGreaterThan(0); expect(primitive.positions.length).toBeGreaterThan(0);
@ -519,12 +613,18 @@ describe('loadObj', () => {
return new Uint16Array(indices.toUint16Buffer().buffer); return new Uint16Array(indices.toUint16Buffer().buffer);
} }
it('applies triangle winding order sanitization', async () => { it("applies triangle winding order sanitization", async () => {
options.triangleWindingOrderSanitization = false; options.triangleWindingOrderSanitization = false;
const indicesIncorrect = await loadAndGetIndices(objIncorrectWindingOrderPath, options); const indicesIncorrect = await loadAndGetIndices(
objIncorrectWindingOrderPath,
options
);
options.triangleWindingOrderSanitization = true; options.triangleWindingOrderSanitization = true;
const indicesCorrect = await loadAndGetIndices(objIncorrectWindingOrderPath, options); const indicesCorrect = await loadAndGetIndices(
objIncorrectWindingOrderPath,
options
);
expect(indicesIncorrect[0]).toBe(0); expect(indicesIncorrect[0]).toBe(0);
expect(indicesIncorrect[2]).toBe(2); expect(indicesIncorrect[2]).toBe(2);
@ -535,53 +635,65 @@ describe('loadObj', () => {
expect(indicesCorrect[1]).toBe(2); expect(indicesCorrect[1]).toBe(2);
}); });
it('throws when position index is out of bounds', async () => { it("throws when position index is out of bounds", async () => {
let thrownError; let thrownError;
try { try {
await loadObj(objIncompletePositionsPath, options); await loadObj(objIncompletePositionsPath, options);
} catch (e) { } catch (e) {
thrownError = e; thrownError = e;
} }
expect(thrownError).toEqual(new RuntimeError('Position index 1 is out of bounds')); expect(thrownError).toEqual(
new RuntimeError("Position index 1 is out of bounds")
);
}); });
it('throws when normal index is out of bounds', async () => { it("throws when normal index is out of bounds", async () => {
let thrownError; let thrownError;
try { try {
await loadObj(objIncompleteNormalsPath, options); await loadObj(objIncompleteNormalsPath, options);
} catch (e) { } catch (e) {
thrownError = e; thrownError = e;
} }
expect(thrownError).toEqual(new RuntimeError('Normal index 1 is out of bounds')); expect(thrownError).toEqual(
new RuntimeError("Normal index 1 is out of bounds")
);
}); });
it('throws when uv index is out of bounds', async () => { it("throws when uv index is out of bounds", async () => {
let thrownError; let thrownError;
try { try {
await loadObj(objIncompleteUvsPath, options); await loadObj(objIncompleteUvsPath, options);
} catch (e) { } catch (e) {
thrownError = e; thrownError = e;
} }
expect(thrownError).toEqual(new RuntimeError('UV index 1 is out of bounds')); expect(thrownError).toEqual(
new RuntimeError("UV index 1 is out of bounds")
);
}); });
it('throws when file has invalid contents', async () => { it("throws when file has invalid contents", async () => {
let thrownError; let thrownError;
try { try {
await loadObj(objInvalidContentsPath, options); await loadObj(objInvalidContentsPath, options);
} catch (e) { } catch (e) {
thrownError = e; thrownError = e;
} }
expect(thrownError).toEqual(new RuntimeError(objInvalidContentsPath + ' does not have any geometry data')); expect(thrownError).toEqual(
new RuntimeError(
objInvalidContentsPath + " does not have any geometry data"
)
);
}); });
it('throw when reading invalid file', async () => { it("throw when reading invalid file", async () => {
let thrownError; let thrownError;
try { try {
await loadObj(objInvalidPath, options); await loadObj(objInvalidPath, options);
} catch (e) { } catch (e) {
thrownError = e; thrownError = e;
} }
expect(thrownError.message.startsWith('ENOENT: no such file or directory')).toBe(true); expect(
thrownError.message.startsWith("ENOENT: no such file or directory")
).toBe(true);
}); });
}); });

View File

@ -1,85 +1,85 @@
'use strict'; "use strict";
const loadTexture = require('../../lib/loadTexture'); const loadTexture = require("../../lib/loadTexture");
const pngTexturePath = 'specs/data/box-complex-material/shininess.png'; const pngTexturePath = "specs/data/box-complex-material/shininess.png";
const jpgTexturePath = 'specs/data/box-complex-material/emission.jpg'; const jpgTexturePath = "specs/data/box-complex-material/emission.jpg";
const jpegTexturePath = 'specs/data/box-complex-material/specular.jpeg'; const jpegTexturePath = "specs/data/box-complex-material/specular.jpeg";
const gifTexturePath = 'specs/data/box-complex-material/ambient.gif'; const gifTexturePath = "specs/data/box-complex-material/ambient.gif";
const grayscaleTexturePath = 'specs/data/box-complex-material-alpha/alpha.png'; const grayscaleTexturePath = "specs/data/box-complex-material-alpha/alpha.png";
const transparentTexturePath = 'specs/data/box-complex-material/diffuse.png'; const transparentTexturePath = "specs/data/box-complex-material/diffuse.png";
describe('loadTexture', () => { describe("loadTexture", () => {
it('loads png texture', async () => { it("loads png texture", async () => {
const texture = await loadTexture(pngTexturePath); const texture = await loadTexture(pngTexturePath);
expect(texture.transparent).toBe(false); expect(texture.transparent).toBe(false);
expect(texture.source).toBeDefined(); expect(texture.source).toBeDefined();
expect(texture.name).toBe('shininess'); expect(texture.name).toBe("shininess");
expect(texture.extension).toBe('.png'); expect(texture.extension).toBe(".png");
expect(texture.path).toBe(pngTexturePath); expect(texture.path).toBe(pngTexturePath);
expect(texture.pixels).toBeUndefined(); expect(texture.pixels).toBeUndefined();
expect(texture.width).toBeUndefined(); expect(texture.width).toBeUndefined();
expect(texture.height).toBeUndefined(); expect(texture.height).toBeUndefined();
}); });
it('loads jpg texture', async () => { it("loads jpg texture", async () => {
const texture = await loadTexture(jpgTexturePath); const texture = await loadTexture(jpgTexturePath);
expect(texture.transparent).toBe(false); expect(texture.transparent).toBe(false);
expect(texture.source).toBeDefined(); expect(texture.source).toBeDefined();
expect(texture.name).toBe('emission'); expect(texture.name).toBe("emission");
expect(texture.extension).toBe('.jpg'); expect(texture.extension).toBe(".jpg");
expect(texture.path).toBe(jpgTexturePath); expect(texture.path).toBe(jpgTexturePath);
expect(texture.pixels).toBeUndefined(); expect(texture.pixels).toBeUndefined();
expect(texture.width).toBeUndefined(); expect(texture.width).toBeUndefined();
expect(texture.height).toBeUndefined(); expect(texture.height).toBeUndefined();
}); });
it('loads jpeg texture', async () => { it("loads jpeg texture", async () => {
const texture = await loadTexture(jpegTexturePath); const texture = await loadTexture(jpegTexturePath);
expect(texture.transparent).toBe(false); expect(texture.transparent).toBe(false);
expect(texture.source).toBeDefined(); expect(texture.source).toBeDefined();
expect(texture.name).toBe('specular'); expect(texture.name).toBe("specular");
expect(texture.extension).toBe('.jpeg'); expect(texture.extension).toBe(".jpeg");
expect(texture.path).toBe(jpegTexturePath); expect(texture.path).toBe(jpegTexturePath);
expect(texture.pixels).toBeUndefined(); expect(texture.pixels).toBeUndefined();
expect(texture.width).toBeUndefined(); expect(texture.width).toBeUndefined();
expect(texture.height).toBeUndefined(); expect(texture.height).toBeUndefined();
}); });
it('loads gif texture', async () => { it("loads gif texture", async () => {
const texture = await loadTexture(gifTexturePath); const texture = await loadTexture(gifTexturePath);
expect(texture.transparent).toBe(false); expect(texture.transparent).toBe(false);
expect(texture.source).toBeDefined(); expect(texture.source).toBeDefined();
expect(texture.name).toBe('ambient'); expect(texture.name).toBe("ambient");
expect(texture.extension).toBe('.gif'); expect(texture.extension).toBe(".gif");
expect(texture.path).toBe(gifTexturePath); expect(texture.path).toBe(gifTexturePath);
expect(texture.pixels).toBeUndefined(); expect(texture.pixels).toBeUndefined();
expect(texture.width).toBeUndefined(); expect(texture.width).toBeUndefined();
expect(texture.height).toBeUndefined(); expect(texture.height).toBeUndefined();
}); });
it('loads grayscale texture', async () => { it("loads grayscale texture", async () => {
const texture = await loadTexture(grayscaleTexturePath); const texture = await loadTexture(grayscaleTexturePath);
expect(texture.transparent).toBe(false); expect(texture.transparent).toBe(false);
expect(texture.source).toBeDefined(); expect(texture.source).toBeDefined();
expect(texture.extension).toBe('.png'); expect(texture.extension).toBe(".png");
}); });
it('loads texture with alpha channel', async () => { it("loads texture with alpha channel", async () => {
const texture = await loadTexture(transparentTexturePath); const texture = await loadTexture(transparentTexturePath);
expect(texture.transparent).toBe(false); expect(texture.transparent).toBe(false);
}); });
it('loads texture with checkTransparency flag', async () => { it("loads texture with checkTransparency flag", async () => {
const options = { const options = {
checkTransparency : true checkTransparency: true,
}; };
const texture = await loadTexture(transparentTexturePath, options); const texture = await loadTexture(transparentTexturePath, options);
expect(texture.transparent).toBe(true); expect(texture.transparent).toBe(true);
}); });
it('loads and decodes png', async () => { it("loads and decodes png", async () => {
const options = { const options = {
decode : true decode: true,
}; };
const texture = await loadTexture(pngTexturePath, options); const texture = await loadTexture(pngTexturePath, options);
expect(texture.pixels).toBeDefined(); expect(texture.pixels).toBeDefined();
@ -87,9 +87,9 @@ describe('loadTexture', () => {
expect(texture.height).toBe(211); expect(texture.height).toBe(211);
}); });
it('loads and decodes jpeg', async () => { it("loads and decodes jpeg", async () => {
const options = { const options = {
decode : true decode: true,
}; };
const texture = await loadTexture(jpegTexturePath, options); const texture = await loadTexture(jpegTexturePath, options);
expect(texture.pixels).toBeDefined(); expect(texture.pixels).toBeDefined();

View File

@ -1,91 +1,93 @@
'use strict'; "use strict";
const { DeveloperError } = require('cesium'); const { DeveloperError } = require("cesium");
const fsExtra = require('fs-extra'); const fsExtra = require("fs-extra");
const path = require('path'); const path = require("path");
const Promise = require('bluebird'); const Promise = require("bluebird");
const createGltf = require('../../lib/createGltf'); const createGltf = require("../../lib/createGltf");
const obj2gltf = require('../../lib/obj2gltf'); const obj2gltf = require("../../lib/obj2gltf");
const texturedObjPath = 'specs/data/box-textured/box-textured.obj'; const texturedObjPath = "specs/data/box-textured/box-textured.obj";
const complexObjPath = 'specs/data/box-complex-material/box-complex-material.obj'; const complexObjPath =
const missingMtllibObjPath = 'specs/data/box-missing-mtllib/box-missing-mtllib.obj'; "specs/data/box-complex-material/box-complex-material.obj";
const missingMtllibObjPath =
"specs/data/box-missing-mtllib/box-missing-mtllib.obj";
const outputDirectory = 'output'; const outputDirectory = "output";
const textureUrl = 'specs/data/box-textured/cesium.png'; const textureUrl = "specs/data/box-textured/cesium.png";
describe('obj2gltf', () => { describe("obj2gltf", () => {
beforeEach(() => { beforeEach(() => {
spyOn(fsExtra, 'outputFile').and.returnValue(Promise.resolve()); spyOn(fsExtra, "outputFile").and.returnValue(Promise.resolve());
}); });
it('converts obj to gltf', async () => { it("converts obj to gltf", async () => {
const gltf = await obj2gltf(texturedObjPath); const gltf = await obj2gltf(texturedObjPath);
expect(gltf).toBeDefined(); expect(gltf).toBeDefined();
expect(gltf.images.length).toBe(1); expect(gltf.images.length).toBe(1);
}); });
it('converts obj to glb', async () => { it("converts obj to glb", async () => {
const options = { const options = {
binary : true binary: true,
}; };
const glb = await obj2gltf(texturedObjPath, options); const glb = await obj2gltf(texturedObjPath, options);
const magic = glb.toString('utf8', 0, 4); const magic = glb.toString("utf8", 0, 4);
expect(magic).toBe('glTF'); expect(magic).toBe("glTF");
}); });
it('convert obj to gltf with separate resources', async () => { it("convert obj to gltf with separate resources", async () => {
const options = { const options = {
separate: true, separate: true,
separateTextures: true, separateTextures: true,
outputDirectory : outputDirectory outputDirectory: outputDirectory,
}; };
await obj2gltf(texturedObjPath, options); await obj2gltf(texturedObjPath, options);
expect(fsExtra.outputFile.calls.count()).toBe(2); // Saves out .png and .bin expect(fsExtra.outputFile.calls.count()).toBe(2); // Saves out .png and .bin
}); });
it('convert obj to gltf with separate resources when buffer exceeds Node limit', async () => { it("convert obj to gltf with separate resources when buffer exceeds Node limit", async () => {
spyOn(createGltf, '_getBufferMaxByteLength').and.returnValue(0); spyOn(createGltf, "_getBufferMaxByteLength").and.returnValue(0);
const options = { const options = {
separate: true, separate: true,
separateTextures: true, separateTextures: true,
outputDirectory : outputDirectory outputDirectory: outputDirectory,
}; };
await obj2gltf(texturedObjPath, options); await obj2gltf(texturedObjPath, options);
expect(fsExtra.outputFile.calls.count()).toBe(5); // Saves out .png and four .bin for positions, normals, uvs, and indices expect(fsExtra.outputFile.calls.count()).toBe(5); // Saves out .png and four .bin for positions, normals, uvs, and indices
}); });
it('converts obj to glb with separate resources', async () => { it("converts obj to glb with separate resources", async () => {
const options = { const options = {
separate: true, separate: true,
separateTextures: true, separateTextures: true,
outputDirectory: outputDirectory, outputDirectory: outputDirectory,
binary : true binary: true,
}; };
await obj2gltf(texturedObjPath, options); await obj2gltf(texturedObjPath, options);
expect(fsExtra.outputFile.calls.count()).toBe(2); // Saves out .png and .bin expect(fsExtra.outputFile.calls.count()).toBe(2); // Saves out .png and .bin
}); });
it('converts obj with multiple textures', async () => { it("converts obj with multiple textures", async () => {
const options = { const options = {
separateTextures: true, separateTextures: true,
outputDirectory : outputDirectory outputDirectory: outputDirectory,
}; };
await obj2gltf(complexObjPath, options); await obj2gltf(complexObjPath, options);
expect(fsExtra.outputFile.calls.count()).toBe(5); // baseColor, metallicRoughness, occlusion, emission, normal expect(fsExtra.outputFile.calls.count()).toBe(5); // baseColor, metallicRoughness, occlusion, emission, normal
}); });
it('sets overriding textures (1)', async () => { it("sets overriding textures (1)", async () => {
const options = { const options = {
overridingTextures: { overridingTextures: {
metallicRoughnessOcclusionTexture: textureUrl, metallicRoughnessOcclusionTexture: textureUrl,
normalTexture: textureUrl, normalTexture: textureUrl,
baseColorTexture: textureUrl, baseColorTexture: textureUrl,
emissiveTexture: textureUrl, emissiveTexture: textureUrl,
alphaTexture : textureUrl alphaTexture: textureUrl,
}, },
separateTextures: true, separateTextures: true,
outputDirectory : outputDirectory outputDirectory: outputDirectory,
}; };
await obj2gltf(complexObjPath, options); await obj2gltf(complexObjPath, options);
const args = fsExtra.outputFile.calls.allArgs(); const args = fsExtra.outputFile.calls.allArgs();
@ -95,7 +97,7 @@ describe('obj2gltf', () => {
} }
}); });
it('sets overriding textures (2)', async () => { it("sets overriding textures (2)", async () => {
const options = { const options = {
overridingTextures: { overridingTextures: {
specularGlossinessTexture: textureUrl, specularGlossinessTexture: textureUrl,
@ -103,10 +105,10 @@ describe('obj2gltf', () => {
normalTexture: textureUrl, normalTexture: textureUrl,
baseColorTexture: textureUrl, baseColorTexture: textureUrl,
emissiveTexture: textureUrl, emissiveTexture: textureUrl,
alphaTexture : textureUrl alphaTexture: textureUrl,
}, },
separateTextures: true, separateTextures: true,
outputDirectory : outputDirectory outputDirectory: outputDirectory,
}; };
await obj2gltf(complexObjPath, options); await obj2gltf(complexObjPath, options);
const args = fsExtra.outputFile.calls.allArgs(); const args = fsExtra.outputFile.calls.allArgs();
@ -116,18 +118,18 @@ describe('obj2gltf', () => {
} }
}); });
it('uses a custom logger', async () => { it("uses a custom logger", async () => {
let lastMessage; let lastMessage;
const options = { const options = {
logger: (message) => { logger: (message) => {
lastMessage = message; lastMessage = message;
} },
}; };
await obj2gltf(missingMtllibObjPath, options); await obj2gltf(missingMtllibObjPath, options);
expect(lastMessage.indexOf('Could not read material file') >= 0).toBe(true); expect(lastMessage.indexOf("Could not read material file") >= 0).toBe(true);
}); });
it('uses a custom writer', async () => { it("uses a custom writer", async () => {
const filePaths = []; const filePaths = [];
const fileContents = []; const fileContents = [];
const options = { const options = {
@ -135,27 +137,27 @@ describe('obj2gltf', () => {
writer: (relativePath, contents) => { writer: (relativePath, contents) => {
filePaths.push(relativePath); filePaths.push(relativePath);
fileContents.push(contents); fileContents.push(contents);
} },
}; };
await obj2gltf(texturedObjPath, options); await obj2gltf(texturedObjPath, options);
expect(filePaths).toEqual(['cesium.png', 'box-textured.bin']); expect(filePaths).toEqual(["cesium.png", "box-textured.bin"]);
expect(fileContents[0]).toBeDefined(); expect(fileContents[0]).toBeDefined();
expect(fileContents[1]).toBeDefined(); expect(fileContents[1]).toBeDefined();
}); });
it('throws if objPath is undefined', () => { it("throws if objPath is undefined", () => {
let thrownError; let thrownError;
try { try {
obj2gltf(undefined); obj2gltf(undefined);
} catch (e) { } catch (e) {
thrownError = e; thrownError = e;
} }
expect(thrownError).toEqual(new DeveloperError('objPath is required')); expect(thrownError).toEqual(new DeveloperError("objPath is required"));
}); });
it('throws if both options.writer and options.outputDirectory are undefined when writing separate resources', () => { it("throws if both options.writer and options.outputDirectory are undefined when writing separate resources", () => {
const options = { const options = {
separateTextures : true separateTextures: true,
}; };
let thrownError; let thrownError;
@ -164,13 +166,17 @@ describe('obj2gltf', () => {
} catch (e) { } catch (e) {
thrownError = e; thrownError = e;
} }
expect(thrownError).toEqual(new DeveloperError('Either options.writer or options.outputDirectory must be defined when writing separate resources.')); expect(thrownError).toEqual(
new DeveloperError(
"Either options.writer or options.outputDirectory must be defined when writing separate resources."
)
);
}); });
it('throws if more than one material type is set', () => { it("throws if more than one material type is set", () => {
const options = { const options = {
metallicRoughness: true, metallicRoughness: true,
specularGlossiness : true specularGlossiness: true,
}; };
let thrownError; let thrownError;
@ -179,15 +185,19 @@ describe('obj2gltf', () => {
} catch (e) { } catch (e) {
thrownError = e; thrownError = e;
} }
expect(thrownError).toEqual(new DeveloperError('Only one material type may be set from [metallicRoughness, specularGlossiness, unlit].')); expect(thrownError).toEqual(
new DeveloperError(
"Only one material type may be set from [metallicRoughness, specularGlossiness, unlit]."
)
);
}); });
it('throws if metallicRoughnessOcclusionTexture and specularGlossinessTexture are both defined', () => { it("throws if metallicRoughnessOcclusionTexture and specularGlossinessTexture are both defined", () => {
const options = { const options = {
overridingTextures: { overridingTextures: {
metallicRoughnessOcclusionTexture: textureUrl, metallicRoughnessOcclusionTexture: textureUrl,
specularGlossinessTexture : textureUrl specularGlossinessTexture: textureUrl,
} },
}; };
let thrownError; let thrownError;
@ -196,6 +206,10 @@ describe('obj2gltf', () => {
} catch (e) { } catch (e) {
thrownError = e; thrownError = e;
} }
expect(thrownError).toEqual(new DeveloperError('metallicRoughnessOcclusionTexture and specularGlossinessTexture cannot both be defined.')); expect(thrownError).toEqual(
new DeveloperError(
"metallicRoughnessOcclusionTexture and specularGlossinessTexture cannot both be defined."
)
);
}); });
}); });