From fae7ae67f06d65dbf04eb25a3c1977179fc34355 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 7 Sep 2016 18:12:29 -0400 Subject: [PATCH 01/69] Update version for line-ending fix --- CHANGES.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 77cb62d..110fada 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ Change Log ========== +### 0.1.6 2016-09-07 + +* Changed obj2gltf.js line endings from CRLF to LF in npm package. + ### 0.1.5 2016-08-26 * Fixed incorrect parameter to the gltf-pipeline. diff --git a/package.json b/package.json index f21b938..2e22bfb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obj2gltf", - "version": "0.1.5", + "version": "0.1.6", "description": "Convert OBJ model format to glTF", "license": "Apache-2.0", "contributors": [ From c09c3924238d325fa8686ea77f6141c3c14e26fb Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Tue, 25 Oct 2016 20:23:27 -0400 Subject: [PATCH 02/69] Use typings instead of rolling our own TypeScript definition solution This gets rid of the TypeScriptDefinitions directory and associated gulp task. Instead it uses the typings module which is a more standard way to manage TS definitions. This now creates a typings folder at npm install type and WebStorm has been configured to simply point to that. Not only is this cleaner, but the actual intellisense is much better at WebStorm should provide even more help now. Also removed the request dependency because it's no loner used. Also added missing entries to npmignore and gitignore. As part of this change, I also updated all npm modules to there latest version, this is in preparation for turning on greenkeeper. --- .gitignore | 4 + .idea/OBJ2GLTF.iml | 3 +- .idea/jsLibraryMappings.xml | 3 +- .idea/libraries/node_modules.xml | 12 - ...{TypeScriptDefinitions.xml => typings.xml} | 5 +- .npmignore | 5 +- TypeScriptDefinitions/async.d.ts | 165 ------ TypeScriptDefinitions/byline.d.ts | 38 -- TypeScriptDefinitions/fs-extra.d.ts | 95 ---- TypeScriptDefinitions/gulp.d.ts | 290 ---------- TypeScriptDefinitions/istanbul.d.ts | 73 --- TypeScriptDefinitions/jasmine.d.ts | 508 ------------------ TypeScriptDefinitions/open.d.ts | 9 - TypeScriptDefinitions/request.d.ts | 262 --------- TypeScriptDefinitions/yargs.d.ts | 170 ------ gulpfile.js | 26 - package.json | 30 +- typings.json | 16 + 18 files changed, 44 insertions(+), 1670 deletions(-) delete mode 100644 .idea/libraries/node_modules.xml rename .idea/libraries/{TypeScriptDefinitions.xml => typings.xml} (59%) delete mode 100644 TypeScriptDefinitions/async.d.ts delete mode 100644 TypeScriptDefinitions/byline.d.ts delete mode 100644 TypeScriptDefinitions/fs-extra.d.ts delete mode 100644 TypeScriptDefinitions/gulp.d.ts delete mode 100644 TypeScriptDefinitions/istanbul.d.ts delete mode 100644 TypeScriptDefinitions/jasmine.d.ts delete mode 100644 TypeScriptDefinitions/open.d.ts delete mode 100644 TypeScriptDefinitions/request.d.ts delete mode 100644 TypeScriptDefinitions/yargs.d.ts create mode 100644 typings.json diff --git a/.gitignore b/.gitignore index 91f2110..ad27f7b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,10 @@ npm-debug.log .idea/workspace.xml .idea/tasks.xml +# TypeScript definitions +typings + # Generate data test coverage +*.tgz diff --git a/.idea/OBJ2GLTF.iml b/.idea/OBJ2GLTF.iml index 7f09720..cc19882 100644 --- a/.idea/OBJ2GLTF.iml +++ b/.idea/OBJ2GLTF.iml @@ -7,7 +7,6 @@ - - + \ No newline at end of file diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml index 09c4a54..45d7418 100644 --- a/.idea/jsLibraryMappings.xml +++ b/.idea/jsLibraryMappings.xml @@ -1,8 +1,7 @@ - - + diff --git a/.idea/libraries/node_modules.xml b/.idea/libraries/node_modules.xml deleted file mode 100644 index c4a1e51..0000000 --- a/.idea/libraries/node_modules.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/TypeScriptDefinitions.xml b/.idea/libraries/typings.xml similarity index 59% rename from .idea/libraries/TypeScriptDefinitions.xml rename to .idea/libraries/typings.xml index 2a75ac2..772980b 100644 --- a/.idea/libraries/TypeScriptDefinitions.xml +++ b/.idea/libraries/typings.xml @@ -1,12 +1,13 @@ - + - + + diff --git a/.npmignore b/.npmignore index ba04ba4..1fe05bd 100644 --- a/.npmignore +++ b/.npmignore @@ -1,9 +1,12 @@ /.idea +/doc /specs /test -/TypeScriptDefinitions +/typings /coverage .jshintrc .npmignore .travis.yml gulpfile.js +typings.json +*.tgz diff --git a/TypeScriptDefinitions/async.d.ts b/TypeScriptDefinitions/async.d.ts deleted file mode 100644 index 6288a26..0000000 --- a/TypeScriptDefinitions/async.d.ts +++ /dev/null @@ -1,165 +0,0 @@ -// Type definitions for Async 1.4.2 -// Project: https://github.com/caolan/async -// Definitions by: Boris Yankov , Arseniy Maximov , Joe Herman -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -interface Dictionary { [key: string]: T; } - -interface ErrorCallback { (err?: Error): void; } -interface AsyncResultCallback { (err: Error, result: T): void; } -interface AsyncResultArrayCallback { (err: Error, results: T[]): void; } -interface AsyncResultObjectCallback { (err: Error, results: Dictionary): void; } - -interface AsyncFunction { (callback: (err?: Error, result?: T) => void): void; } -interface AsyncIterator { (item: T, callback: ErrorCallback): void; } -interface AsyncForEachOfIterator { (item: T, key: number|string, callback: ErrorCallback): void; } -interface AsyncResultIterator { (item: T, callback: AsyncResultCallback): void; } -interface AsyncMemoIterator { (memo: R, item: T, callback: AsyncResultCallback): void; } -interface AsyncBooleanIterator { (item: T, callback: (err: string, truthValue: boolean) => void): void; } - -interface AsyncWorker { (task: T, callback: ErrorCallback): void; } -interface AsyncVoidFunction { (callback: ErrorCallback): void; } - -interface AsyncQueue { - length(): number; - started: boolean; - running(): number; - idle(): boolean; - concurrency: number; - push(task: T, callback?: ErrorCallback): void; - push(task: T[], callback?: ErrorCallback): void; - unshift(task: T, callback?: ErrorCallback): void; - unshift(task: T[], callback?: ErrorCallback): void; - saturated: () => any; - empty: () => any; - drain: () => any; - paused: boolean; - pause(): void - resume(): void; - kill(): void; -} - -interface AsyncPriorityQueue { - length(): number; - concurrency: number; - started: boolean; - paused: boolean; - push(task: T, priority: number, callback?: AsyncResultArrayCallback): void; - push(task: T[], priority: number, callback?: AsyncResultArrayCallback): void; - saturated: () => any; - empty: () => any; - drain: () => any; - running(): number; - idle(): boolean; - pause(): void; - resume(): void; - kill(): void; -} - -interface AsyncCargo { - length(): number; - payload: number; - push(task: any, callback? : Function): void; - push(task: any[], callback? : Function): void; - saturated(): void; - empty(): void; - drain(): void; - idle(): boolean; - pause(): void; - resume(): void; - kill(): void; -} - -interface Async { - - // Collections - each(arr: T[], iterator: AsyncIterator, callback?: ErrorCallback): void; - eachSeries(arr: T[], iterator: AsyncIterator, callback?: ErrorCallback): void; - eachLimit(arr: T[], limit: number, iterator: AsyncIterator, callback?: ErrorCallback): void; - forEachOf(obj: any, iterator: (item: any, key: string|number, callback?: ErrorCallback) => void, callback: ErrorCallback): void; - forEachOf(obj: T[], iterator: AsyncForEachOfIterator, callback?: ErrorCallback): void; - forEachOfSeries(obj: any, iterator: (item: any, key: string|number, callback?: ErrorCallback) => void, callback: ErrorCallback): void; - forEachOfSeries(obj: T[], iterator: AsyncForEachOfIterator, callback?: ErrorCallback): void; - forEachOfLimit(obj: any, limit: number, iterator: (item: any, key: string|number, callback?: ErrorCallback) => void, callback: ErrorCallback): void; - forEachOfLimit(obj: T[], limit: number, iterator: AsyncForEachOfIterator, callback?: ErrorCallback): void; - map(arr: T[], iterator: AsyncResultIterator, callback?: AsyncResultArrayCallback): any; - mapSeries(arr: T[], iterator: AsyncResultIterator, callback?: AsyncResultArrayCallback): any; - mapLimit(arr: T[], limit: number, iterator: AsyncResultIterator, callback?: AsyncResultArrayCallback): any; - filter(arr: T[], iterator: AsyncBooleanIterator, callback?: AsyncResultArrayCallback): any; - select(arr: T[], iterator: AsyncBooleanIterator, callback?: AsyncResultArrayCallback): any; - filterSeries(arr: T[], iterator: AsyncBooleanIterator, callback?: AsyncResultArrayCallback): any; - selectSeries(arr: T[], iterator: AsyncBooleanIterator, callback?: AsyncResultArrayCallback): any; - filterLimit(arr: T[], limit: number, iterator: AsyncBooleanIterator, callback?: AsyncResultArrayCallback): any; - selectLimit(arr: T[], limit: number, iterator: AsyncBooleanIterator, callback?: AsyncResultArrayCallback): any; - reject(arr: T[], iterator: AsyncBooleanIterator, callback?: AsyncResultArrayCallback): any; - rejectSeries(arr: T[], iterator: AsyncBooleanIterator, callback?: AsyncResultArrayCallback): any; - rejectLimit(arr: T[], limit: number, iterator: AsyncBooleanIterator, callback?: AsyncResultArrayCallback): any; - reduce(arr: T[], memo: R, iterator: AsyncMemoIterator, callback?: AsyncResultCallback): any; - inject(arr: T[], memo: R, iterator: AsyncMemoIterator, callback?: AsyncResultCallback): any; - foldl(arr: T[], memo: R, iterator: AsyncMemoIterator, callback?: AsyncResultCallback): any; - reduceRight(arr: T[], memo: R, iterator: AsyncMemoIterator, callback: AsyncResultCallback): any; - foldr(arr: T[], memo: R, iterator: AsyncMemoIterator, callback: AsyncResultCallback): any; - detect(arr: T[], iterator: AsyncBooleanIterator, callback?: AsyncResultCallback): any; - detectSeries(arr: T[], iterator: AsyncBooleanIterator, callback?: AsyncResultCallback): any; - detectLimit(arr: T[], limit: number, iterator: AsyncBooleanIterator, callback?: AsyncResultCallback): any; - sortBy(arr: T[], iterator: AsyncResultIterator, callback?: AsyncResultArrayCallback): any; - some(arr: T[], iterator: AsyncBooleanIterator, callback?: (result: boolean) => void): any; - someLimit(arr: T[], limit: number, iterator: AsyncBooleanIterator, callback?: (result: boolean) => void): any; - any(arr: T[], iterator: AsyncBooleanIterator, callback?: (result: boolean) => void): any; - every(arr: T[], iterator: AsyncBooleanIterator, callback?: (result: boolean) => any): any; - everyLimit(arr: T[], limit: number, iterator: AsyncBooleanIterator, callback?: (result: boolean) => any): any; - all(arr: T[], iterator: AsyncBooleanIterator, callback?: (result: boolean) => any): any; - concat(arr: T[], iterator: AsyncResultIterator, callback?: AsyncResultArrayCallback): any; - concatSeries(arr: T[], iterator: AsyncResultIterator, callback?: AsyncResultArrayCallback): any; - - // Control Flow - series(tasks: AsyncFunction[], callback?: AsyncResultArrayCallback): void; - series(tasks: Dictionary>, callback?: AsyncResultObjectCallback): void; - parallel(tasks: Array>, callback?: AsyncResultArrayCallback): void; - parallel(tasks: Dictionary>, callback?: AsyncResultObjectCallback): void; - parallelLimit(tasks: Array>, limit: number, callback?: AsyncResultArrayCallback): void; - parallelLimit(tasks: Dictionary>, limit: number, callback?: AsyncResultObjectCallback): void; - whilst(test: () => boolean, fn: AsyncVoidFunction, callback: (err: any) => void): void; - doWhilst(fn: AsyncVoidFunction, test: () => boolean, callback: (err: any) => void): void; - until(test: () => boolean, fn: AsyncVoidFunction, callback: (err: any) => void): void; - doUntil(fn: AsyncVoidFunction, test: () => boolean, callback: (err: any) => void): void; - during(test: (testCallback : (error: Error, truth: boolean) => void) => void, fn: AsyncVoidFunction, callback: (err: any) => void): void; - doDuring(fn: AsyncVoidFunction, test: (testCallback: (error: Error, truth: boolean) => void) => void, callback: (err: any) => void): void; - forever(next: (errCallback : (err: Error) => void) => void, errBack: (err: Error) => void) : void; - waterfall(tasks: Function[], callback?: (err: Error, results?: any) => void): void; - compose(...fns: Function[]): Function; - seq(...fns: Function[]): Function; - applyEach(fns: Function[], argsAndCallback: any[]): void; // applyEach(fns, args..., callback). TS does not support ... for a middle argument. Callback is optional. - applyEachSeries(fns: Function[], argsAndCallback: any[]): void; // applyEachSeries(fns, args..., callback). TS does not support ... for a middle argument. Callback is optional. - queue(worker: AsyncWorker, concurrency?: number): AsyncQueue; - priorityQueue(worker: AsyncWorker, concurrency: number): AsyncPriorityQueue; - cargo(worker : (tasks: any[], callback : ErrorCallback) => void, payload? : number) : AsyncCargo; - auto(tasks: any, callback?: (error: Error, results: any) => void): void; - retry(opts: number, task: (callback : AsyncResultCallback, results: any) => void, callback: (error: Error, results: any) => void): void; - retry(opts: { times: number, interval: number|((retryCount: number) => number) }, task: (callback: AsyncResultCallback, results : any) => void, callback: (error: Error, results: any) => void): void; - iterator(tasks: Function[]): Function; - apply(fn: Function, ...arguments: any[]): AsyncFunction; - nextTick(callback: Function): void; - setImmediate(callback: Function): void; - - times (n: number, iterator: AsyncResultIterator, callback: AsyncResultArrayCallback): void; - timesSeries(n: number, iterator: AsyncResultIterator, callback: AsyncResultArrayCallback): void; - timesLimit(n: number, limit: number, iterator: AsyncResultIterator, callback: AsyncResultArrayCallback): void; - - // Utils - memoize(fn: Function, hasher?: Function): Function; - unmemoize(fn: Function): Function; - ensureAsync(fn: (... argsAndCallback: any[]) => void): Function; - constant(...values: any[]): Function; - asyncify(fn: Function): Function; - wrapSync(fn: Function): Function; - log(fn: Function, ...arguments: any[]): void; - dir(fn: Function, ...arguments: any[]): void; - noConflict(): Async; -} - -declare var async: Async; - -declare module "async" { - export = async; -} diff --git a/TypeScriptDefinitions/byline.d.ts b/TypeScriptDefinitions/byline.d.ts deleted file mode 100644 index 48ddd2d..0000000 --- a/TypeScriptDefinitions/byline.d.ts +++ /dev/null @@ -1,38 +0,0 @@ -// Type definitions for byline 4.2.1 -// Project: https://github.com/jahewson/node-byline -// Definitions by: Stefan Steinhart -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -/// - -declare module "byline" { - import stream = require("stream"); - - export interface LineStreamOptions extends stream.TransformOptions { - keepEmptyLines?: boolean; - } - - export interface LineStream extends stream.Transform { - } - - export interface LineStreamCreatable extends LineStream { - new (options?:LineStreamOptions):LineStream - } - - //TODO is it possible to declare static factory functions without name (directly on the module) - // - // JS: - // // convinience API - // module.exports = function(readStream, options) { - // return module.exports.createStream(readStream, options); - // }; - // - // TS: - // ():LineStream; // same as createStream():LineStream - // (stream:stream.Stream, options?:LineStreamOptions):LineStream; // same as createStream(stream, options?):LineStream - - export function createStream():LineStream; - export function createStream(stream:NodeJS.ReadableStream, options?:LineStreamOptions):LineStream; - - export var LineStream:LineStreamCreatable; -} diff --git a/TypeScriptDefinitions/fs-extra.d.ts b/TypeScriptDefinitions/fs-extra.d.ts deleted file mode 100644 index 39656ce..0000000 --- a/TypeScriptDefinitions/fs-extra.d.ts +++ /dev/null @@ -1,95 +0,0 @@ -// Type definitions for fs-extra -// Project: https://github.com/jprichardson/node-fs-extra -// Definitions by: midknight41 -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -// Imported from: https://github.com/soywiz/typescript-node-definitions/fs-extra.d.ts - -/// - -declare module "fs-extra" { - export * from "fs"; - - export function copy(src: string, dest: string, callback?: (err: Error) => void): void; - export function copy(src: string, dest: string, filter: CopyFilter, callback?: (err: Error) => void): void; - export function copy(src: string, dest: string, options: CopyOptions, callback?: (err: Error) => void): void; - - export function copySync(src: string, dest: string): void; - export function copySync(src: string, dest: string, filter: CopyFilter): void; - export function copySync(src: string, dest: string, options: CopyOptions): void; - - export function createFile(file: string, callback?: (err: Error) => void): void; - export function createFileSync(file: string): void; - - export function mkdirs(dir: string, callback?: (err: Error) => void): void; - export function mkdirp(dir: string, callback?: (err: Error) => void): void; - export function mkdirs(dir: string, options?: MkdirOptions, callback?: (err: Error) => void): void; - export function mkdirp(dir: string, options?: MkdirOptions, callback?: (err: Error) => void): void; - export function mkdirsSync(dir: string, options?: MkdirOptions): void; - export function mkdirpSync(dir: string, options?: MkdirOptions): void; - - export function outputFile(file: string, data: any, callback?: (err: Error) => void): void; - export function outputFileSync(file: string, data: any): void; - - export function outputJson(file: string, data: any, callback?: (err: Error) => void): void; - export function outputJSON(file: string, data: any, callback?: (err: Error) => void): void; - export function outputJsonSync(file: string, data: any): void; - export function outputJSONSync(file: string, data: any): void; - - export function readJson(file: string, callback: (err: Error, jsonObject: any) => void): void; - export function readJson(file: string, options: OpenOptions, callback: (err: Error, jsonObject: any) => void): void; - export function readJSON(file: string, callback: (err: Error, jsonObject: any) => void): void; - export function readJSON(file: string, options: OpenOptions, callback: (err: Error, jsonObject: any) => void): void; - - export function readJsonSync(file: string, options?: OpenOptions): any; - export function readJSONSync(file: string, options?: OpenOptions): any; - - export function remove(dir: string, callback?: (err: Error) => void): void; - export function removeSync(dir: string): void; - - export function writeJson(file: string, object: any, callback?: (err: Error) => void): void; - export function writeJson(file: string, object: any, options?: OpenOptions, callback?: (err: Error) => void): void; - export function writeJSON(file: string, object: any, callback?: (err: Error) => void): void; - export function writeJSON(file: string, object: any, options?: OpenOptions, callback?: (err: Error) => void): void; - - export function writeJsonSync(file: string, object: any, options?: OpenOptions): void; - export function writeJSONSync(file: string, object: any, options?: OpenOptions): void; - - export function ensureDir(path: string, cb: (err: Error) => void): void; - export function ensureDirSync(path: string): void; - - export function ensureFile(path: string, cb: (err: Error) => void): void; - export function ensureFileSync(path: string): void; - - export function ensureLink(path: string, cb: (err: Error) => void): void; - export function ensureLinkSync(path: string): void; - - export function ensureSymlink(path: string, cb: (err: Error) => void): void; - export function ensureSymlinkSync(path: string): void; - - export function emptyDir(path: string, callback?: (err: Error) => void): void; - export function emptyDirSync(path: string): boolean; - - export interface CopyFilterFunction { - (src: string): boolean - } - - export type CopyFilter = CopyFilterFunction | RegExp; - - export interface CopyOptions { - clobber?: boolean - preserveTimestamps?: boolean - dereference?: boolean - filter?: CopyFilter - } - - export interface OpenOptions { - encoding?: string; - flag?: string; - } - - export interface MkdirOptions { - fs?: any; - mode?: number; - } -} diff --git a/TypeScriptDefinitions/gulp.d.ts b/TypeScriptDefinitions/gulp.d.ts deleted file mode 100644 index 43bb5aa..0000000 --- a/TypeScriptDefinitions/gulp.d.ts +++ /dev/null @@ -1,290 +0,0 @@ -// Type definitions for Gulp v3.8.x -// Project: http://gulpjs.com -// Definitions by: Drew Noakes -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -/// -/// - -declare module "gulp" { - import Orchestrator = require("orchestrator"); - - namespace gulp { - interface Gulp extends Orchestrator { - /** - * Define a task - * @param name The name of the task. - * @param deps An array of task names to be executed and completed before your task will run. - * @param fn The function that performs the task's operations. For asynchronous tasks, you need to provide a hint when the task is complete: - *
    - *
  • Take in a callback
  • - *
  • Return a stream or a promise
  • - *
- */ - task: Orchestrator.AddMethod; - /** - * Emits files matching provided glob or an array of globs. Returns a stream of Vinyl files that can be piped to plugins. - * @param glob Glob or array of globs to read. - * @param opt Options to pass to node-glob through glob-stream. - */ - src: SrcMethod; - /** - * Can be piped to and it will write files. Re-emits all data passed to it so you can pipe to multiple folders. - * Folders that don't exist will be created. - * - * @param outFolder The path (output folder) to write files to. Or a function that returns it, the function will be provided a vinyl File instance. - * @param opt - */ - dest: DestMethod; - /** - * Watch files and do something when a file changes. This always returns an EventEmitter that emits change events. - * - * @param glob a single glob or array of globs that indicate which files to watch for changes. - * @param opt options, that are passed to the gaze library. - * @param fn a callback or array of callbacks to be called on each change, or names of task(s) to run when a file changes, added with task(). - */ - watch: WatchMethod; - } - - interface GulpPlugin { - (...args: any[]): NodeJS.ReadWriteStream; - } - - interface WatchMethod { - /** - * Watch files and do something when a file changes. This always returns an EventEmitter that emits change events. - * - * @param glob a single glob or array of globs that indicate which files to watch for changes. - * @param fn a callback or array of callbacks to be called on each change, or names of task(s) to run when a file changes, added with task(). - */ - (glob: string|string[], fn: (WatchCallback|string)): NodeJS.EventEmitter; - /** - * Watch files and do something when a file changes. This always returns an EventEmitter that emits change events. - * - * @param glob a single glob or array of globs that indicate which files to watch for changes. - * @param fn a callback or array of callbacks to be called on each change, or names of task(s) to run when a file changes, added with task(). - */ - (glob: string|string[], fn: (WatchCallback|string)[]): NodeJS.EventEmitter; - /** - * Watch files and do something when a file changes. This always returns an EventEmitter that emits change events. - * - * @param glob a single glob or array of globs that indicate which files to watch for changes. - * @param opt options, that are passed to the gaze library. - * @param fn a callback or array of callbacks to be called on each change, or names of task(s) to run when a file changes, added with task(). - */ - (glob: string|string[], opt: WatchOptions, fn: (WatchCallback|string)): NodeJS.EventEmitter; - /** - * Watch files and do something when a file changes. This always returns an EventEmitter that emits change events. - * - * @param glob a single glob or array of globs that indicate which files to watch for changes. - * @param opt options, that are passed to the gaze library. - * @param fn a callback or array of callbacks to be called on each change, or names of task(s) to run when a file changes, added with task(). - */ - (glob: string|string[], opt: WatchOptions, fn: (WatchCallback|string)[]): NodeJS.EventEmitter; - - } - - interface DestMethod { - /** - * Can be piped to and it will write files. Re-emits all data passed to it so you can pipe to multiple folders. - * Folders that don't exist will be created. - * - * @param outFolder The path (output folder) to write files to. Or a function that returns it, the function will be provided a vinyl File instance. - * @param opt - */ - (outFolder: string|((file: string) => string), opt?: DestOptions): NodeJS.ReadWriteStream; - } - - interface SrcMethod { - /** - * Emits files matching provided glob or an array of globs. Returns a stream of Vinyl files that can be piped to plugins. - * @param glob Glob or array of globs to read. - * @param opt Options to pass to node-glob through glob-stream. - */ - (glob: string|string[], opt?: SrcOptions): NodeJS.ReadWriteStream; - } - - /** - * Options to pass to node-glob through glob-stream. - * Specifies two options in addition to those used by node-glob: - * https://github.com/isaacs/node-glob#options - */ - interface SrcOptions { - /** - * Setting this to false will return file.contents as null - * and not read the file at all. - * Default: true. - */ - read?: boolean; - - /** - * Setting this to false will return file.contents as a stream and not buffer files. - * This is useful when working with large files. - * Note: Plugins might not implement support for streams. - * Default: true. - */ - buffer?: boolean; - - /** - * The base path of a glob. - * - * Default is everything before a glob starts. - */ - base?: string; - - /** - * The current working directory in which to search. - * Defaults to process.cwd(). - */ - cwd?: string; - - /** - * The place where patterns starting with / will be mounted onto. - * Defaults to path.resolve(options.cwd, "/") (/ on Unix systems, and C:\ or some such on Windows.) - */ - root?: string; - - /** - * Include .dot files in normal matches and globstar matches. - * Note that an explicit dot in a portion of the pattern will always match dot files. - */ - dot?: boolean; - - /** - * By default, a pattern starting with a forward-slash will be "mounted" onto the root setting, so that a valid - * filesystem path is returned. Set this flag to disable that behavior. - */ - nomount?: boolean; - - /** - * Add a / character to directory matches. Note that this requires additional stat calls. - */ - mark?: boolean; - - /** - * Don't sort the results. - */ - nosort?: boolean; - - /** - * Set to true to stat all results. This reduces performance somewhat, and is completely unnecessary, unless - * readdir is presumed to be an untrustworthy indicator of file existence. It will cause ELOOP to be triggered one - * level sooner in the case of cyclical symbolic links. - */ - stat?: boolean; - - /** - * When an unusual error is encountered when attempting to read a directory, a warning will be printed to stderr. - * Set the silent option to true to suppress these warnings. - */ - silent?: boolean; - - /** - * When an unusual error is encountered when attempting to read a directory, the process will just continue on in - * search of other matches. Set the strict option to raise an error in these cases. - */ - strict?: boolean; - - /** - * See cache property above. Pass in a previously generated cache object to save some fs calls. - */ - cache?: boolean; - - /** - * A cache of results of filesystem information, to prevent unnecessary stat calls. - * While it should not normally be necessary to set this, you may pass the statCache from one glob() call to the - * options object of another, if you know that the filesystem will not change between calls. - */ - statCache?: boolean; - - /** - * Perform a synchronous glob search. - */ - sync?: boolean; - - /** - * In some cases, brace-expanded patterns can result in the same file showing up multiple times in the result set. - * By default, this implementation prevents duplicates in the result set. Set this flag to disable that behavior. - */ - nounique?: boolean; - - /** - * Set to never return an empty set, instead returning a set containing the pattern itself. - * This is the default in glob(3). - */ - nonull?: boolean; - - /** - * Perform a case-insensitive match. Note that case-insensitive filesystems will sometimes result in glob returning - * results that are case-insensitively matched anyway, since readdir and stat will not raise an error. - */ - nocase?: boolean; - - /** - * Set to enable debug logging in minimatch and glob. - */ - debug?: boolean; - - /** - * Set to enable debug logging in glob, but not minimatch. - */ - globDebug?: boolean; - } - - interface DestOptions { - /** - * The output folder. Only has an effect if provided output folder is relative. - * Default: process.cwd() - */ - cwd?: string; - - /** - * Octal permission string specifying mode for any folders that need to be created for output folder. - * Default: 0777. - */ - mode?: string; - } - - /** - * Options that are passed to gaze. - * https://github.com/shama/gaze - */ - interface WatchOptions { - /** Interval to pass to fs.watchFile. */ - interval?: number; - /** Delay for events called in succession for the same file/event. */ - debounceDelay?: number; - /** Force the watch mode. Either 'auto' (default), 'watch' (force native events), or 'poll' (force stat polling). */ - mode?: string; - /** The current working directory to base file patterns from. Default is process.cwd().. */ - cwd?: string; - } - - interface WatchEvent { - /** The type of change that occurred, either added, changed or deleted. */ - type: string; - /** The path to the file that triggered the event. */ - path: string; - } - - /** - * Callback to be called on each watched file change. - */ - interface WatchCallback { - (event: WatchEvent): void; - } - - interface TaskCallback { - /** - * Defines a task. - * Tasks may be made asynchronous if they are passing a callback or return a promise or a stream. - * @param cb callback used to signal asynchronous completion. Caller includes err in case of error. - */ - (cb?: (err?: any) => void): any; - } - } - - var gulp: gulp.Gulp; - - export = gulp; -} diff --git a/TypeScriptDefinitions/istanbul.d.ts b/TypeScriptDefinitions/istanbul.d.ts deleted file mode 100644 index 1ccbd14..0000000 --- a/TypeScriptDefinitions/istanbul.d.ts +++ /dev/null @@ -1,73 +0,0 @@ -// Type definitions for Istanbul v0.4.0 -// Project: https://github.com/gotwarlost/istanbul -// Definitions by: Tanguy Krotoff -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -declare module 'istanbul' { - namespace istanbul { - interface Istanbul { - new (options?: any): Istanbul; - Collector: Collector; - config: Config; - ContentWriter: ContentWriter; - FileWriter: FileWriter; - hook: Hook; - Instrumenter: Instrumenter; - Report: Report; - Reporter: Reporter; - Store: Store; - utils: ObjectUtils; - VERSION: string; - Writer: Writer; - } - - interface Collector { - new (options?: any): Collector; - add(coverage: any, testName?: string): void; - } - - interface Config { - } - - interface ContentWriter { - } - - interface FileWriter { - } - - interface Hook { - } - - interface Instrumenter { - new (options?: any): Instrumenter; - instrumentSync(code: string, filename: string): string; - } - - interface Report { - } - - interface Configuration { - new (obj: any, overrides: any): Configuration; - } - - interface Reporter { - new (cfg?: Configuration, dir?: string): Reporter; - add(fmt: string): void; - addAll(fmts: Array): void; - write(collector: Collector, sync: boolean, callback: Function): void; - } - - interface Store { - } - - interface ObjectUtils { - } - - interface Writer { - } - } - - var istanbul: istanbul.Istanbul; - - export = istanbul; -} diff --git a/TypeScriptDefinitions/jasmine.d.ts b/TypeScriptDefinitions/jasmine.d.ts deleted file mode 100644 index 538aca3..0000000 --- a/TypeScriptDefinitions/jasmine.d.ts +++ /dev/null @@ -1,508 +0,0 @@ -// Type definitions for Jasmine 2.2 -// Project: http://jasmine.github.io/ -// Definitions by: Boris Yankov , Theodore Brown , David Pärsson -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - - -// For ddescribe / iit use : https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/karma-jasmine/karma-jasmine.d.ts - -declare function describe(description: string, specDefinitions: () => void): void; -declare function fdescribe(description: string, specDefinitions: () => void): void; -declare function xdescribe(description: string, specDefinitions: () => void): void; - -declare function it(expectation: string, assertion?: () => void, timeout?: number): void; -declare function it(expectation: string, assertion?: (done: DoneFn) => void, timeout?: number): void; -declare function fit(expectation: string, assertion?: () => void, timeout?: number): void; -declare function fit(expectation: string, assertion?: (done: DoneFn) => void, timeout?: number): void; -declare function xit(expectation: string, assertion?: () => void, timeout?: number): void; -declare function xit(expectation: string, assertion?: (done: DoneFn) => void, timeout?: number): void; - -/** If you call the function pending anywhere in the spec body, no matter the expectations, the spec will be marked pending. */ -declare function pending(reason?: string): void; - -declare function beforeEach(action: () => void, timeout?: number): void; -declare function beforeEach(action: (done: DoneFn) => void, timeout?: number): void; -declare function afterEach(action: () => void, timeout?: number): void; -declare function afterEach(action: (done: DoneFn) => void, timeout?: number): void; - -declare function beforeAll(action: () => void, timeout?: number): void; -declare function beforeAll(action: (done: DoneFn) => void, timeout?: number): void; -declare function afterAll(action: () => void, timeout?: number): void; -declare function afterAll(action: (done: DoneFn) => void, timeout?: number): void; - -declare function expect(spy: Function): jasmine.Matchers; -declare function expect(actual: any): jasmine.Matchers; - -declare function fail(e?: any): void; -/** Action method that should be called when the async work is complete */ -interface DoneFn extends Function { - (): void; - - /** fails the spec and indicates that it has completed. If the message is an Error, Error.message is used */ - fail: (message?: Error|string) => void; -} - -declare function spyOn(object: any, method: string): jasmine.Spy; - -declare function runs(asyncMethod: Function): void; -declare function waitsFor(latchMethod: () => boolean, failureMessage?: string, timeout?: number): void; -declare function waits(timeout?: number): void; - -declare namespace jasmine { - - var clock: () => Clock; - - function any(aclass: any): Any; - function anything(): Any; - function arrayContaining(sample: any[]): ArrayContaining; - function objectContaining(sample: any): ObjectContaining; - function createSpy(name: string, originalFn?: Function): Spy; - function createSpyObj(baseName: string, methodNames: any[]): any; - function createSpyObj(baseName: string, methodNames: any[]): T; - function pp(value: any): string; - function getEnv(): Env; - function addCustomEqualityTester(equalityTester: CustomEqualityTester): void; - function addMatchers(matchers: CustomMatcherFactories): void; - function stringMatching(str: string): Any; - function stringMatching(str: RegExp): Any; - - interface Any { - - new (expectedClass: any): any; - - jasmineMatches(other: any): boolean; - jasmineToString(): string; - } - - // taken from TypeScript lib.core.es6.d.ts, applicable to CustomMatchers.contains() - interface ArrayLike { - length: number; - [n: number]: T; - } - - interface ArrayContaining { - new (sample: any[]): any; - - asymmetricMatch(other: any): boolean; - jasmineToString(): string; - } - - interface ObjectContaining { - new (sample: any): any; - - jasmineMatches(other: any, mismatchKeys: any[], mismatchValues: any[]): boolean; - jasmineToString(): string; - } - - interface Block { - - new (env: Env, func: SpecFunction, spec: Spec): any; - - execute(onComplete: () => void): void; - } - - interface WaitsBlock extends Block { - new (env: Env, timeout: number, spec: Spec): any; - } - - interface WaitsForBlock extends Block { - new (env: Env, timeout: number, latchFunction: SpecFunction, message: string, spec: Spec): any; - } - - interface Clock { - install(): void; - uninstall(): void; - /** Calls to any registered callback are triggered when the clock is ticked forward via the jasmine.clock().tick function, which takes a number of milliseconds. */ - tick(ms: number): void; - mockDate(date?: Date): void; - } - - interface CustomEqualityTester { - (first: any, second: any): boolean; - } - - interface CustomMatcher { - compare(actual: T, expected: T): CustomMatcherResult; - compare(actual: any, expected: any): CustomMatcherResult; - } - - interface CustomMatcherFactory { - (util: MatchersUtil, customEqualityTesters: Array): CustomMatcher; - } - - interface CustomMatcherFactories { - [index: string]: CustomMatcherFactory; - } - - interface CustomMatcherResult { - pass: boolean; - message?: string; - } - - interface MatchersUtil { - equals(a: any, b: any, customTesters?: Array): boolean; - contains(haystack: ArrayLike | string, needle: any, customTesters?: Array): boolean; - buildFailureMessage(matcherName: string, isNot: boolean, actual: any, ...expected: Array): string; - } - - interface Env { - setTimeout: any; - clearTimeout: void; - setInterval: any; - clearInterval: void; - updateInterval: number; - - currentSpec: Spec; - - matchersClass: Matchers; - - version(): any; - versionString(): string; - nextSpecId(): number; - addReporter(reporter: Reporter): void; - execute(): void; - describe(description: string, specDefinitions: () => void): Suite; - // ddescribe(description: string, specDefinitions: () => void): Suite; Not a part of jasmine. Angular team adds these - beforeEach(beforeEachFunction: () => void): void; - beforeAll(beforeAllFunction: () => void): void; - currentRunner(): Runner; - afterEach(afterEachFunction: () => void): void; - afterAll(afterAllFunction: () => void): void; - xdescribe(desc: string, specDefinitions: () => void): XSuite; - it(description: string, func: () => void): Spec; - // iit(description: string, func: () => void): Spec; Not a part of jasmine. Angular team adds these - xit(desc: string, func: () => void): XSpec; - compareRegExps_(a: RegExp, b: RegExp, mismatchKeys: string[], mismatchValues: string[]): boolean; - compareObjects_(a: any, b: any, mismatchKeys: string[], mismatchValues: string[]): boolean; - equals_(a: any, b: any, mismatchKeys: string[], mismatchValues: string[]): boolean; - contains_(haystack: any, needle: any): boolean; - addCustomEqualityTester(equalityTester: CustomEqualityTester): void; - addMatchers(matchers: CustomMatcherFactories): void; - specFilter(spec: Spec): boolean; - throwOnExpectationFailure(value: boolean): void; - } - - interface FakeTimer { - - new (): any; - - reset(): void; - tick(millis: number): void; - runFunctionsWithinRange(oldMillis: number, nowMillis: number): void; - scheduleFunction(timeoutKey: any, funcToCall: () => void, millis: number, recurring: boolean): void; - } - - interface HtmlReporter { - new (): any; - } - - interface HtmlSpecFilter { - new (): any; - } - - interface Result { - type: string; - } - - interface NestedResults extends Result { - description: string; - - totalCount: number; - passedCount: number; - failedCount: number; - - skipped: boolean; - - rollupCounts(result: NestedResults): void; - log(values: any): void; - getItems(): Result[]; - addResult(result: Result): void; - passed(): boolean; - } - - interface MessageResult extends Result { - values: any; - trace: Trace; - } - - interface ExpectationResult extends Result { - matcherName: string; - passed(): boolean; - expected: any; - actual: any; - message: string; - trace: Trace; - } - - interface Trace { - name: string; - message: string; - stack: any; - } - - interface PrettyPrinter { - - new (): any; - - format(value: any): void; - iterateObject(obj: any, fn: (property: string, isGetter: boolean) => void): void; - emitScalar(value: any): void; - emitString(value: string): void; - emitArray(array: any[]): void; - emitObject(obj: any): void; - append(value: any): void; - } - - interface StringPrettyPrinter extends PrettyPrinter { - } - - interface Queue { - - new (env: any): any; - - env: Env; - ensured: boolean[]; - blocks: Block[]; - running: boolean; - index: number; - offset: number; - abort: boolean; - - addBefore(block: Block, ensure?: boolean): void; - add(block: any, ensure?: boolean): void; - insertNext(block: any, ensure?: boolean): void; - start(onComplete?: () => void): void; - isRunning(): boolean; - next_(): void; - results(): NestedResults; - } - - interface Matchers { - - new (env: Env, actual: any, spec: Env, isNot?: boolean): any; - - env: Env; - actual: any; - spec: Env; - isNot?: boolean; - message(): any; - - toBe(expected: any, expectationFailOutput?: any): boolean; - toEqual(expected: any, expectationFailOutput?: any): boolean; - toMatch(expected: string | RegExp, expectationFailOutput?: any): boolean; - toBeDefined(expectationFailOutput?: any): boolean; - toBeUndefined(expectationFailOutput?: any): boolean; - toBeNull(expectationFailOutput?: any): boolean; - toBeNaN(): boolean; - toBeTruthy(expectationFailOutput?: any): boolean; - toBeFalsy(expectationFailOutput?: any): boolean; - toHaveBeenCalled(): boolean; - toHaveBeenCalledWith(...params: any[]): boolean; - toHaveBeenCalledTimes(expected: number): boolean; - toContain(expected: any, expectationFailOutput?: any): boolean; - toBeLessThan(expected: number, expectationFailOutput?: any): boolean; - toBeGreaterThan(expected: number, expectationFailOutput?: any): boolean; - toBeCloseTo(expected: number, precision?: any, expectationFailOutput?: any): boolean; - toThrow(expected?: any): boolean; - toThrowError(message?: string | RegExp): boolean; - toThrowError(expected?: new (...args: any[]) => Error, message?: string | RegExp): boolean; - not: Matchers; - - Any: Any; - } - - interface Reporter { - reportRunnerStarting(runner: Runner): void; - reportRunnerResults(runner: Runner): void; - reportSuiteResults(suite: Suite): void; - reportSpecStarting(spec: Spec): void; - reportSpecResults(spec: Spec): void; - log(str: string): void; - } - - interface MultiReporter extends Reporter { - addReporter(reporter: Reporter): void; - } - - interface Runner { - - new (env: Env): any; - - execute(): void; - beforeEach(beforeEachFunction: SpecFunction): void; - afterEach(afterEachFunction: SpecFunction): void; - beforeAll(beforeAllFunction: SpecFunction): void; - afterAll(afterAllFunction: SpecFunction): void; - finishCallback(): void; - addSuite(suite: Suite): void; - add(block: Block): void; - specs(): Spec[]; - suites(): Suite[]; - topLevelSuites(): Suite[]; - results(): NestedResults; - } - - interface SpecFunction { - (spec?: Spec): void; - } - - interface SuiteOrSpec { - id: number; - env: Env; - description: string; - queue: Queue; - } - - interface Spec extends SuiteOrSpec { - - new (env: Env, suite: Suite, description: string): any; - - suite: Suite; - - afterCallbacks: SpecFunction[]; - spies_: Spy[]; - - results_: NestedResults; - matchersClass: Matchers; - - getFullName(): string; - results(): NestedResults; - log(arguments: any): any; - runs(func: SpecFunction): Spec; - addToQueue(block: Block): void; - addMatcherResult(result: Result): void; - expect(actual: any): any; - waits(timeout: number): Spec; - waitsFor(latchFunction: SpecFunction, timeoutMessage?: string, timeout?: number): Spec; - fail(e?: any): void; - getMatchersClass_(): Matchers; - addMatchers(matchersPrototype: CustomMatcherFactories): void; - finishCallback(): void; - finish(onComplete?: () => void): void; - after(doAfter: SpecFunction): void; - execute(onComplete?: () => void): any; - addBeforesAndAftersToQueue(): void; - explodes(): void; - spyOn(obj: any, methodName: string, ignoreMethodDoesntExist: boolean): Spy; - removeAllSpies(): void; - } - - interface XSpec { - id: number; - runs(): void; - } - - interface Suite extends SuiteOrSpec { - - new (env: Env, description: string, specDefinitions: () => void, parentSuite: Suite): any; - - parentSuite: Suite; - - getFullName(): string; - finish(onComplete?: () => void): void; - beforeEach(beforeEachFunction: SpecFunction): void; - afterEach(afterEachFunction: SpecFunction): void; - beforeAll(beforeAllFunction: SpecFunction): void; - afterAll(afterAllFunction: SpecFunction): void; - results(): NestedResults; - add(suiteOrSpec: SuiteOrSpec): void; - specs(): Spec[]; - suites(): Suite[]; - children(): any[]; - execute(onComplete?: () => void): void; - } - - interface XSuite { - execute(): void; - } - - interface Spy { - (...params: any[]): any; - - identity: string; - and: SpyAnd; - calls: Calls; - mostRecentCall: { args: any[]; }; - argsForCall: any[]; - wasCalled: boolean; - } - - interface SpyAnd { - /** By chaining the spy with and.callThrough, the spy will still track all calls to it but in addition it will delegate to the actual implementation. */ - callThrough(): Spy; - /** By chaining the spy with and.returnValue, all calls to the function will return a specific value. */ - returnValue(val: any): Spy; - /** By chaining the spy with and.returnValues, all calls to the function will return specific values in order until it reaches the end of the return values list. */ - returnValues(...values: any[]): Spy; - /** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied function. */ - callFake(fn: Function): Spy; - /** By chaining the spy with and.throwError, all calls to the spy will throw the specified value. */ - throwError(msg: string): Spy; - /** When a calling strategy is used for a spy, the original stubbing behavior can be returned at any time with and.stub. */ - stub(): Spy; - } - - interface Calls { - /** By chaining the spy with calls.any(), will return false if the spy has not been called at all, and then true once at least one call happens. **/ - any(): boolean; - /** By chaining the spy with calls.count(), will return the number of times the spy was called **/ - count(): number; - /** By chaining the spy with calls.argsFor(), will return the arguments passed to call number index **/ - argsFor(index: number): any[]; - /** By chaining the spy with calls.allArgs(), will return the arguments to all calls **/ - allArgs(): any[]; - /** By chaining the spy with calls.all(), will return the context (the this) and arguments passed all calls **/ - all(): CallInfo[]; - /** By chaining the spy with calls.mostRecent(), will return the context (the this) and arguments for the most recent call **/ - mostRecent(): CallInfo; - /** By chaining the spy with calls.first(), will return the context (the this) and arguments for the first call **/ - first(): CallInfo; - /** By chaining the spy with calls.reset(), will clears all tracking for a spy **/ - reset(): void; - } - - interface CallInfo { - /** The context (the this) for the call */ - object: any; - /** All arguments passed to the call */ - args: any[]; - /** The return value of the call */ - returnValue: any; - } - - interface Util { - inherit(childClass: Function, parentClass: Function): any; - formatException(e: any): any; - htmlEscape(str: string): string; - argsToArray(args: any): any; - extend(destination: any, source: any): any; - } - - interface JsApiReporter extends Reporter { - - started: boolean; - finished: boolean; - result: any; - messages: any; - - new (): any; - - suites(): Suite[]; - summarize_(suiteOrSpec: SuiteOrSpec): any; - results(): any; - resultsForSpec(specId: any): any; - log(str: any): any; - resultsForSpecs(specIds: any): any; - summarizeResult_(result: any): any; - } - - interface Jasmine { - Spec: Spec; - clock: Clock; - util: Util; - } - - export var HtmlReporter: HtmlReporter; - export var HtmlSpecFilter: HtmlSpecFilter; - export var DEFAULT_TIMEOUT_INTERVAL: number; -} diff --git a/TypeScriptDefinitions/open.d.ts b/TypeScriptDefinitions/open.d.ts deleted file mode 100644 index bca02e5..0000000 --- a/TypeScriptDefinitions/open.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Type definitions for open 0.0.3 -// Project: https://github.com/jjrdn/node-open -// Definitions by: Bart van der Schoor -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -declare module 'open' { - function open(target: string, app?: string): void; - export = open; -} diff --git a/TypeScriptDefinitions/request.d.ts b/TypeScriptDefinitions/request.d.ts deleted file mode 100644 index 547ee62..0000000 --- a/TypeScriptDefinitions/request.d.ts +++ /dev/null @@ -1,262 +0,0 @@ -// Type definitions for request -// Project: https://github.com/mikeal/request -// Definitions by: Carlos Ballesteros Velasco , bonnici , Bart van der Schoor , Joe Skeen , Christopher Currens -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -// Imported from: https://github.com/soywiz/typescript-node-definitions/d.ts - -/// -/// - -declare module 'request' { - import stream = require('stream'); - import http = require('http'); - import https = require('https'); - import FormData = require('form-data'); - import url = require('url'); - import fs = require('fs'); - - namespace request { - export interface RequestAPI { - - defaults(options: TOptions): RequestAPI; - defaults(options: RequiredUriUrl & TOptions): DefaultUriUrlRequestApi; - - (uri: string, options?: TOptions, callback?: RequestCallback): TRequest; - (uri: string, callback?: RequestCallback): TRequest; - (options: TUriUrlOptions & TOptions, callback?: RequestCallback): TRequest; - - get(uri: string, options?: TOptions, callback?: RequestCallback): TRequest; - get(uri: string, callback?: RequestCallback): TRequest; - get(options: TUriUrlOptions & TOptions, callback?: RequestCallback): TRequest; - - post(uri: string, options?: TOptions, callback?: RequestCallback): TRequest; - post(uri: string, callback?: RequestCallback): TRequest; - post(options: TUriUrlOptions & TOptions, callback?: RequestCallback): TRequest; - - put(uri: string, options?: TOptions, callback?: RequestCallback): TRequest; - put(uri: string, callback?: RequestCallback): TRequest; - put(options: TUriUrlOptions & TOptions, callback?: RequestCallback): TRequest; - - head(uri: string, options?: TOptions, callback?: RequestCallback): TRequest; - head(uri: string, callback?: RequestCallback): TRequest; - head(options: TUriUrlOptions & TOptions, callback?: RequestCallback): TRequest; - - patch(uri: string, options?: TOptions, callback?: RequestCallback): TRequest; - patch(uri: string, callback?: RequestCallback): TRequest; - patch(options: TUriUrlOptions & TOptions, callback?: RequestCallback): TRequest; - - del(uri: string, options?: TOptions, callback?: RequestCallback): TRequest; - del(uri: string, callback?: RequestCallback): TRequest; - del(options: TUriUrlOptions & TOptions, callback?: RequestCallback): TRequest; - - forever(agentOptions: any, optionsArg: any): TRequest; - jar(): CookieJar; - cookie(str: string): Cookie; - - initParams: any; - debug: boolean; - } - - interface DefaultUriUrlRequestApi extends RequestAPI { - - defaults(options: TOptions): DefaultUriUrlRequestApi; - (): TRequest; - get(): TRequest; - post(): TRequest; - put(): TRequest; - head(): TRequest; - patch(): TRequest; - del(): TRequest; - } - - interface CoreOptions { - baseUrl?: string; - callback?: (error: any, response: http.IncomingMessage, body: any) => void; - jar?: any; // CookieJar - formData?: any; // Object - form?: any; // Object or string - auth?: AuthOptions; - oauth?: OAuthOptions; - aws?: AWSOptions; - hawk?: HawkOptions; - qs?: any; - json?: any; - multipart?: RequestPart[] | Multipart; - agent?: http.Agent | https.Agent; - agentOptions?: any; - agentClass?: any; - forever?: any; - host?: string; - port?: number; - method?: string; - headers?: Headers; - body?: any; - followRedirect?: boolean | ((response: http.IncomingMessage) => boolean); - followAllRedirects?: boolean; - maxRedirects?: number; - encoding?: string; - pool?: any; - timeout?: number; - proxy?: any; - strictSSL?: boolean; - gzip?: boolean; - preambleCRLF?: boolean; - postambleCRLF?: boolean; - key?: Buffer; - cert?: Buffer; - passphrase?: string; - ca?: string | Buffer | string[] | Buffer[]; - har?: HttpArchiveRequest; - useQuerystring?: boolean; - } - - interface UriOptions { - uri: string; - } - interface UrlOptions { - url: string; - } - export type RequiredUriUrl = UriOptions | UrlOptions; - - interface OptionalUriUrl { - uri?: string; - url?: string; - } - - export type OptionsWithUri = UriOptions & CoreOptions; - export type OptionsWithUrl = UrlOptions & CoreOptions; - export type Options = OptionsWithUri | OptionsWithUrl; - - export interface RequestCallback { - (error: any, response: http.IncomingMessage, body: any): void; - } - - export interface HttpArchiveRequest { - url?: string; - method?: string; - headers?: NameValuePair[]; - postData?: { - mimeType?: string; - params?: NameValuePair[]; - } - } - - export interface NameValuePair { - name: string; - value: string; - } - - export interface Multipart { - chunked?: boolean; - data?: { - 'content-type'?: string, - body: string - }[]; - } - - export interface RequestPart { - headers?: Headers; - body: any; - } - - export interface Request extends stream.Stream { - readable: boolean; - writable: boolean; - - getAgent(): http.Agent; - //start(): void; - //abort(): void; - pipeDest(dest: any): void; - setHeader(name: string, value: string, clobber?: boolean): Request; - setHeaders(headers: Headers): Request; - qs(q: Object, clobber?: boolean): Request; - form(): FormData.FormData; - form(form: any): Request; - multipart(multipart: RequestPart[]): Request; - json(val: any): Request; - aws(opts: AWSOptions, now?: boolean): Request; - auth(username: string, password: string, sendInmediately?: boolean, bearer?: string): Request; - oauth(oauth: OAuthOptions): Request; - jar(jar: CookieJar): Request; - - on(event: string, listener: Function): this; - on(event: 'request', listener: (req: http.ClientRequest) => void): this; - on(event: 'response', listener: (resp: http.IncomingMessage) => void): this; - on(event: 'data', listener: (data: Buffer | string) => void): this; - on(event: 'error', listener: (e: Error) => void): this; - on(event: 'complete', listener: (resp: http.IncomingMessage, body?: string | Buffer) => void): this; - - write(buffer: Buffer, cb?: Function): boolean; - write(str: string, cb?: Function): boolean; - write(str: string, encoding: string, cb?: Function): boolean; - write(str: string, encoding?: string, fd?: string): boolean; - end(): void; - end(chunk: Buffer, cb?: Function): void; - end(chunk: string, cb?: Function): void; - end(chunk: string, encoding: string, cb?: Function): void; - pause(): void; - resume(): void; - abort(): void; - destroy(): void; - toJSON(): Object; - } - - export interface Headers { - [key: string]: any; - } - - export interface AuthOptions { - user?: string; - username?: string; - pass?: string; - password?: string; - sendImmediately?: boolean; - bearer?: string; - } - - export interface OAuthOptions { - callback?: string; - consumer_key?: string; - consumer_secret?: string; - token?: string; - token_secret?: string; - verifier?: string; - } - - export interface HawkOptions { - credentials: any; - } - - export interface AWSOptions { - secret: string; - bucket?: string; - } - - export interface CookieJar { - setCookie(cookie: Cookie, uri: string | url.Url, options?: any): void - getCookieString(uri: string | url.Url): string - getCookies(uri: string | url.Url): Cookie[] - } - - export interface CookieValue { - name: string; - value: any; - httpOnly: boolean; - } - - export interface Cookie extends Array { - constructor(name: string, req: Request): void; - str: string; - expires: Date; - path: string; - toString(): string; - } - } - var request: request.RequestAPI; - export = request; -} diff --git a/TypeScriptDefinitions/yargs.d.ts b/TypeScriptDefinitions/yargs.d.ts deleted file mode 100644 index 7dc1ef2..0000000 --- a/TypeScriptDefinitions/yargs.d.ts +++ /dev/null @@ -1,170 +0,0 @@ -// Type definitions for yargs -// Project: https://github.com/chevex/yargs -// Definitions by: Martin Poelstra -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -declare module "yargs" { - - namespace yargs { - interface Argv { - argv: any; - (...args: any[]): any; - parse(...args: any[]): any; - - reset(): Argv; - - locale(): string; - locale(loc:string): Argv; - - detectLocale(detect:boolean): Argv; - - alias(shortName: string, longName: string): Argv; - alias(aliases: { [shortName: string]: string }): Argv; - alias(aliases: { [shortName: string]: string[] }): Argv; - - array(key: string): Argv; - array(keys: string[]): Argv; - - default(key: string, value: any): Argv; - default(defaults: { [key: string]: any}): Argv; - - demand(key: string, msg: string): Argv; - demand(key: string, required?: boolean): Argv; - demand(keys: string[], msg: string): Argv; - demand(keys: string[], required?: boolean): Argv; - demand(positionals: number, required?: boolean): Argv; - demand(positionals: number, msg: string): Argv; - - require(key: string, msg: string): Argv; - require(key: string, required: boolean): Argv; - require(keys: number[], msg: string): Argv; - require(keys: number[], required: boolean): Argv; - require(positionals: number, required: boolean): Argv; - require(positionals: number, msg: string): Argv; - - required(key: string, msg: string): Argv; - required(key: string, required: boolean): Argv; - required(keys: number[], msg: string): Argv; - required(keys: number[], required: boolean): Argv; - required(positionals: number, required: boolean): Argv; - required(positionals: number, msg: string): Argv; - - requiresArg(key: string): Argv; - requiresArg(keys: string[]): Argv; - - describe(key: string, description: string): Argv; - describe(descriptions: { [key: string]: string }): Argv; - - option(key: string, options: Options): Argv; - option(options: { [key: string]: Options }): Argv; - options(key: string, options: Options): Argv; - options(options: { [key: string]: Options }): Argv; - - usage(message: string, options?: { [key: string]: Options }): Argv; - usage(options?: { [key: string]: Options }): Argv; - - command(command: string, description: string): Argv; - command(command: string, description: string, handler: (args: Argv) => void): Argv; - command(command: string, description: string, builder: (args: Argv) => Options): Argv; - command(command: string, description: string, builder: { [optionName: string]: Options }): Argv; - command(command: string, description: string, builder: { [optionName: string]: Options }, handler: (args: Argv) => void): Argv; - command(command: string, description: string, builder: (args: Argv) => Options, handler: (args: Argv) => void): Argv; - - completion(cmd: string, fn?: SyncCompletionFunction): Argv; - completion(cmd: string, description?: string, fn?: SyncCompletionFunction): Argv; - completion(cmd: string, fn?: AsyncCompletionFunction): Argv; - completion(cmd: string, description?: string, fn?: AsyncCompletionFunction): Argv; - - example(command: string, description: string): Argv; - - check(func: (argv: any, aliases: { [alias: string]: string }) => any): Argv; - - boolean(key: string): Argv; - boolean(keys: string[]): Argv; - - string(key: string): Argv; - string(keys: string[]): Argv; - - choices(choices: Object): Argv; - choices(key: string, values:any[]): Argv; - - config(key: string): Argv; - config(keys: string[]): Argv; - - wrap(columns: number): Argv; - - strict(): Argv; - - help(): string; - help(option: string, description?: string): Argv; - - env(prefix?: string): Argv; - env(enable: boolean): Argv; - - epilog(msg: string): Argv; - epilogue(msg: string): Argv; - - version(version: string, option?: string, description?: string): Argv; - version(version: () => string, option?: string, description?: string): Argv; - - showHelpOnFail(enable: boolean, message?: string): Argv; - - showHelp(func?: (message: string) => any): Argv; - - exitProcess(enabled:boolean): Argv; - - global(key: string): Argv; - global(keys: string[]): Argv; - - group(key: string, groupName: string): Argv; - group(keys: string[], groupName: string): Argv; - - nargs(key: string, count: number): Argv; - nargs(nargs: { [key: string]: number }): Argv; - - /* Undocumented */ - - normalize(key: string): Argv; - normalize(keys: string[]): Argv; - - implies(key: string, value: string): Argv; - implies(implies: { [key: string]: string }): Argv; - - count(key: string): Argv; - count(keys: string[]): Argv; - - fail(func: (msg: string) => any): void; - } - - interface Options { - type?: string; - group?: string; - alias?: any; - demand?: any; - required?: any; - require?: any; - default?: any; - defaultDescription?: string; - boolean?: boolean; - string?: boolean; - count?: boolean; - describe?: any; - description?: any; - desc?: any; - requiresArg?: any; - choices?:string[]; - global?: boolean; - array?: boolean; - config?: boolean; - number?: boolean; - normalize?: boolean; - nargs?: number; - } - - type SyncCompletionFunction = (current: string, argv: any) => string[]; - type AsyncCompletionFunction = (current: string, argv: any, done: (completion: string[]) => void) => void; - } - - var yargs: yargs.Argv; - export = yargs; -} diff --git a/gulpfile.js b/gulpfile.js index 313aa31..f479227 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -9,7 +9,6 @@ var Jasmine = require('jasmine'); var JasmineSpecReporter = require('jasmine-spec-reporter'); var open = require('open'); var path = require('path'); -var request = require('request'); var yargs = require('yargs'); var defined = Cesium.defined; @@ -79,28 +78,3 @@ gulp.task('coverage', function () { }); open('coverage/lcov-report/index.html'); }); - -function copyModule(module) { - var tsName = module + '.d.ts'; - var srcUrl = 'https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/master/' + module + '/' + tsName; - var desPath = path.join('TypeScriptDefinitions', tsName); - - request.get({ - url: srcUrl - }, function (error, response) { - if (error) { - console.log(error); - return; - } - if (response.statusCode >= 200 && response.statusCode < 300) { - fsExtra.outputFileSync(desPath, response.body); - } - }); -} - -gulp.task('update-ts-definitions', function () { - fsExtra.removeSync('TypeScriptDefinitions'); - var packageJson = require('./package.json'); - Object.keys(packageJson.dependencies).forEach(copyModule); - Object.keys(packageJson.devDependencies).forEach(copyModule); -}); diff --git a/package.json b/package.json index 2e22bfb..68971ac 100644 --- a/package.json +++ b/package.json @@ -26,33 +26,33 @@ "node": ">=4.0.0" }, "dependencies": { - "async": "2.0.0-rc.6", - "bluebird": "^3.4.1", - "byline": "4.2.1", - "cesium": "1.23.0", + "async": "2.1.2", + "bluebird": "3.4.6", + "byline": "5.0.0", + "cesium": "1.26.0", "fs-extra": "0.30.0", "gltf-pipeline": "0.1.0-alpha4", - "yargs": "4.7.1" + "yargs": "6.3.0" }, "devDependencies": { "gulp": "3.9.1", - "gulp-jshint": "2.0.1", - "istanbul": "0.4.4", - "jasmine": "2.4.1", - "jasmine-spec-reporter": "2.5.0", - "jshint": "2.9.2", - "jshint-stylish": "2.2.0", + "gulp-jshint": "2.0.2", + "istanbul": "0.4.5", + "jasmine": "2.5.2", + "jasmine-spec-reporter": "2.7.0", + "jshint": "2.9.4", + "jshint-stylish": "2.2.1", "open": "0.0.5", - "request": "2.72.0", - "requirejs": "2.2.0" + "requirejs": "2.3.2", + "typings": "1.4.0" }, "scripts": { + "prepublish": "typings install", "jsHint": "gulp jsHint", "jsHint-watch": "gulp jsHint-watch", "test": "gulp test", "test-watch": "gulp test-watch", - "coverage": "gulp coverage", - "update-ts-definitions": "gulp update-ts-definitions" + "coverage": "gulp coverage" }, "bin": { "obj2gltf": "./bin/obj2gltf.js" diff --git a/typings.json b/typings.json new file mode 100644 index 0000000..1eaaaa8 --- /dev/null +++ b/typings.json @@ -0,0 +1,16 @@ +{ + "dependencies": { + "async": "registry:npm/async#2.0.1+20160815105832", + "bluebird": "registry:npm/bluebird#3.4.1+20160909132857", + "gulp": "registry:npm/gulp#4.0.0-alpha.2+20160817105618", + "requirejs": "registry:npm/requirejs#2.2.0+20160319062357", + "yargs": "registry:npm/yargs#5.0.0+20160907000723" + }, + "globalDependencies": { + "byline": "registry:dt/byline#4.2.1+20161006132146", + "fs-extra": "registry:dt/fs-extra#0.0.0+20161004190449", + "istanbul": "registry:dt/istanbul#0.4.0+20160316155526", + "jasmine": "registry:dt/jasmine#2.5.0+20161003201800", + "open": "registry:dt/open#0.0.3+20160316155526" + } +} From 99dc02b7427c0d27b1f1040ca44040ee54571d54 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 26 Oct 2016 23:23:13 -0400 Subject: [PATCH 03/69] Removed old line --- .idea/libraries/typings.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/.idea/libraries/typings.xml b/.idea/libraries/typings.xml index 772980b..9e440e4 100644 --- a/.idea/libraries/typings.xml +++ b/.idea/libraries/typings.xml @@ -6,7 +6,6 @@ - From 77cccf9f90e469dbfb39d6aa33c765de30e04bd9 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 12 Dec 2016 10:13:44 -0500 Subject: [PATCH 04/69] Upgrade gltf-pipeline --- .idea/misc.xml | 10 ---------- package.json | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index fa7d715..e01539a 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,14 +3,4 @@ - - - - - - - - - -
\ No newline at end of file diff --git a/package.json b/package.json index 68971ac..563a403 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "byline": "5.0.0", "cesium": "1.26.0", "fs-extra": "0.30.0", - "gltf-pipeline": "0.1.0-alpha4", + "gltf-pipeline": "0.1.0-alpha6", "yargs": "6.3.0" }, "devDependencies": { From d8408e3f2c48a23dfab0a63d4f89f1c6e00de7c4 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 13 Dec 2016 14:13:40 -0500 Subject: [PATCH 05/69] Update gltf-pipeline --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 563a403..75a2b70 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "byline": "5.0.0", "cesium": "1.26.0", "fs-extra": "0.30.0", - "gltf-pipeline": "0.1.0-alpha6", + "gltf-pipeline": "0.1.0-alpha8", "yargs": "6.3.0" }, "devDependencies": { From 6f5bc856ed09a86a528d14d45e1b824d86506ad5 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 3 Jan 2017 21:37:15 -0500 Subject: [PATCH 06/69] Update gltf-pipeline and general project cleanup --- .gitignore | 10 ++++++---- .npmignore | 3 ++- .travis.yml | 7 ++++++- CHANGES.md | 5 +++++ gulpfile.js | 2 +- package.json | 42 +++++++++++++++++++++++------------------- 6 files changed, 43 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index ad27f7b..b04cad6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,14 +2,16 @@ node_modules npm-debug.log +# TypeScript definitions +typings + # WebStorm user-specific .idea/workspace.xml .idea/tasks.xml -# TypeScript definitions -typings - # Generate data -test coverage +doc +output +test *.tgz diff --git a/.npmignore b/.npmignore index 1fe05bd..dec66d7 100644 --- a/.npmignore +++ b/.npmignore @@ -1,9 +1,10 @@ /.idea +/coverage /doc +/output /specs /test /typings -/coverage .jshintrc .npmignore .travis.yml diff --git a/.travis.yml b/.travis.yml index a73b51f..32c35f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,11 @@ language: node_js node_js: - - 4 + - "4" + - "6" script: - npm run jsHint -- --failTaskOnError - npm run test -- --failTaskOnError --suppressPassed +after_success: +## We only need to run coveralls for one node version (doesn't matter which one). +## We also ignore publishing failures, since restarting an existing travis build would otherwise break. + - if node --version | grep -q ^v6 ; npm run coveralls ; fi diff --git a/CHANGES.md b/CHANGES.md index 110fada..0584792 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ Change Log ========== +### 0.1.7 2017-01-03 + +* Update gltf-pipeline to 0.1.0-alpha9 +* Added command to generate documentation (npm run jsdoc) + ### 0.1.6 2016-09-07 * Changed obj2gltf.js line endings from CRLF to LF in npm package. diff --git a/gulpfile.js b/gulpfile.js index f479227..66d574e 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -20,7 +20,7 @@ var environmentSeparator = process.platform === 'win32' ? ';' : ':'; var nodeBinaries = path.join(__dirname, 'node_modules', '.bin'); process.env.PATH += environmentSeparator + nodeBinaries; -var jsHintFiles = ['**/*.js', '!node_modules/**', '!coverage/**']; +var jsHintFiles = ['**/*.js', '!node_modules/**', '!coverage/**', '!doc/**']; var specFiles = ['**/*.js', '!node_modules/**', '!coverage/**']; gulp.task('jsHint', function () { diff --git a/package.json b/package.json index 75a2b70..5a56146 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obj2gltf", - "version": "0.1.6", + "version": "0.1.7", "description": "Convert OBJ model format to glTF", "license": "Apache-2.0", "contributors": [ @@ -26,33 +26,37 @@ "node": ">=4.0.0" }, "dependencies": { - "async": "2.1.2", - "bluebird": "3.4.6", - "byline": "5.0.0", - "cesium": "1.26.0", - "fs-extra": "0.30.0", - "gltf-pipeline": "0.1.0-alpha8", - "yargs": "6.3.0" + "async": "^2.1.4", + "bluebird": "^3.4.7", + "byline": "^5.0.0", + "cesium": "^1.29.0", + "fs-extra": "^1.0.0", + "gltf-pipeline": "^0.1.0-alpha9", + "yargs": "^6.6.0" }, "devDependencies": { - "gulp": "3.9.1", - "gulp-jshint": "2.0.2", - "istanbul": "0.4.5", - "jasmine": "2.5.2", - "jasmine-spec-reporter": "2.7.0", - "jshint": "2.9.4", - "jshint-stylish": "2.2.1", - "open": "0.0.5", - "requirejs": "2.3.2", - "typings": "1.4.0" + "coveralls": "^2.11.15", + "gulp": "^3.9.1", + "gulp-jshint": "^2.0.4", + "istanbul": "^0.4.5", + "jasmine": "^2.5.2", + "jasmine-spec-reporter": "^3.0.0", + "jsdoc": "^3.4.3", + "jshint": "^2.9.4", + "jshint-stylish": "^2.2.1", + "open": "^0.0.5", + "requirejs": "^2.3.2", + "typings": "^2.1.0" }, "scripts": { "prepublish": "typings install", + "jsdoc": "jsdoc ./lib -R ./README.md -d doc", "jsHint": "gulp jsHint", "jsHint-watch": "gulp jsHint-watch", "test": "gulp test", "test-watch": "gulp test-watch", - "coverage": "gulp coverage" + "coverage": "gulp coverage", + "coveralls": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls" }, "bin": { "obj2gltf": "./bin/obj2gltf.js" From b91e0e87310a59ac7359a91983f4dcff42ce38ae Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 4 Jan 2017 13:10:51 -0500 Subject: [PATCH 07/69] Jasmine spec reporter fix --- gulpfile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 66d574e..c40039d 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -6,7 +6,7 @@ var fsExtra = require('fs-extra'); var gulp = require('gulp'); var gulpJshint = require('gulp-jshint'); var Jasmine = require('jasmine'); -var JasmineSpecReporter = require('jasmine-spec-reporter'); +var SpecReporter = require('jasmine-spec-reporter').SpecReporter; var open = require('open'); var path = require('path'); var yargs = require('yargs'); @@ -42,7 +42,7 @@ gulp.task('jsHint-watch', function () { gulp.task('test', function (done) { var jasmine = new Jasmine(); jasmine.loadConfigFile('specs/jasmine.json'); - jasmine.addReporter(new JasmineSpecReporter({ + jasmine.addReporter(new SpecReporter({ displaySuccessfulSpec: !defined(argv.suppressPassed) || !argv.suppressPassed })); jasmine.execute(); From b382a6374e86ac66381e077ded171e12e7a93654 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 6 Jan 2017 11:36:44 -0500 Subject: [PATCH 08/69] Updated README and LICENSE --- LICENSE.md | 26 ++++++++++++++++++++++++++ README.md | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/LICENSE.md b/LICENSE.md index 8938747..abfee94 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -35,6 +35,32 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +### bluebird + +https://www.npmjs.com/package/bluebird + +> The MIT License (MIT) +> +> Copyright (c) 2013-2015 Petka Antonov +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + ### byline https://www.npmjs.com/package/byline diff --git a/README.md b/README.md index f1e7784..4469ba5 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,45 @@ Using obj2gltf as a command-line tool: |`--ao`|Apply ambient occlusion to the converted model.|No, default `false`| |`-h`|Display help|No| +## Build Instructions + +Run the tests: +``` +npm run test +``` +To run JSHint on the entire codebase, run: +``` +npm run jsHint +``` +To run JSHint automatically when a file is saved, run the following and leave it open in a console window: +``` +npm run jsHint-watch +``` + +### Running Test Coverage + +Coverage uses [istanbul](https://github.com/gotwarlost/istanbul). Run: +``` +npm run coverage +``` +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. + +## Generating Documentation + +To generate the documentation: +``` +npm run jsdoc +``` + +The documentation will be placed in the `doc` folder. + +### Debugging + +* To debug the tests in Webstorm, open the Gulp tab, right click the `test` task, and click `Debug 'test'`. +* To run a single test, change the test function from `it` to `fit`. + ## Contributions Pull requests are appreciated. Please use the same [Contributor License Agreement (CLA)](https://github.com/AnalyticalGraphicsInc/cesium/blob/master/CONTRIBUTING.md) used for [Cesium](http://cesiumjs.org/). From bc27653d5ef25d3906499d57b087b38027c0e834 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 6 Jan 2017 14:09:46 -0500 Subject: [PATCH 09/69] Fix broken link in documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4469ba5..6971918 100644 --- a/README.md +++ b/README.md @@ -92,5 +92,5 @@ Pull requests are appreciated. Please use the same [Contributor License Agreeme Developed by the Cesium team.

- +

From b5c366e2c70a50a53f841440324ea2142493630a Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 6 Jan 2017 17:49:38 -0500 Subject: [PATCH 10/69] Fix AO option --- lib/convert.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/convert.js b/lib/convert.js index 8a135d8..0242177 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -43,7 +43,7 @@ function convert(objFile, outputPath, options) { return createGltf(data, inputPath, modelName); }) .then(function(gltf) { - var aoOptions = ao ? {} : undefined; + var aoOptions = ao ? {enable : true} : undefined; var options = { binary: binary, embed: embed, From 233d163b4cbfdf94a18a63adf7aaf57f0b0918d2 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 6 Jan 2017 17:50:19 -0500 Subject: [PATCH 11/69] Update date --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 0584792..b0db92d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,7 @@ Change Log ========== -### 0.1.7 2017-01-03 +### 0.1.7 2017-01-06 * Update gltf-pipeline to 0.1.0-alpha9 * Added command to generate documentation (npm run jsdoc) From b8b118bca0e66578fa11007e0cedee2ba5b2f7b6 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 13 Mar 2017 15:28:51 -0400 Subject: [PATCH 12/69] Refactor and tests --- .gitignore | 10 +- .idea/modules.xml | 2 +- .idea/{OBJ2GLTF.iml => obj2gltf.iml} | 0 .npmignore | 1 + .travis.yml | 5 + LICENSE.md | 121 +-- README.md | 18 +- bin/obj2gltf.js | 143 +++- gulpfile.js | 12 +- lib/ArrayStorage.js | 107 +++ lib/clone.js | 54 ++ lib/convert.js | 141 +++- lib/gltf.js | 562 +++++++------ lib/image.js | 92 ++- lib/mtl.js | 210 +++-- lib/obj.js | 740 ++++++++++-------- lib/readLines.js | 27 + package.json | 41 +- specs/data/BoxTextured/CesiumLogoFlat.png | Bin 22051 -> 0 bytes specs/data/box-complex-material/alpha.png | Bin 0 -> 1843 bytes specs/data/box-complex-material/ambient.gif | Bin 0 -> 4040 bytes .../box-complex-material.mtl | 20 + .../box-complex-material.obj} | 22 +- specs/data/box-complex-material/bump.png | Bin 0 -> 4720 bytes specs/data/box-complex-material/diffuse.png | Bin 0 -> 8889 bytes specs/data/box-complex-material/emission.jpg | Bin 0 -> 7092 bytes specs/data/box-complex-material/shininess.png | Bin 0 -> 4094 bytes specs/data/box-complex-material/specular.jpeg | Bin 0 -> 5618 bytes specs/data/box-groups/box-groups.mtl | 32 + specs/data/box-groups/box-groups.obj | 132 ++++ .../box-missing-mtllib/box-missing-mtllib.obj | 46 ++ .../box-missing-texture.mtl} | 8 +- .../box-missing-texture.obj | 46 ++ specs/data/box-mtllib/box-mtllib-blue.mtl | 12 + specs/data/box-mtllib/box-mtllib-green.mtl | 12 + specs/data/box-mtllib/box-mtllib-red.mtl | 12 + specs/data/box-mtllib/box-mtllib.obj | 50 ++ .../box-multiple-materials.mtl | 32 + .../box-multiple-materials.obj | 49 ++ .../box-negative-indices.mtl | 12 + .../box-negative-indices.obj | 20 + .../box-no-materials/box-no-materials.obj | 125 +++ specs/data/box-normals/box-normals.mtl | 12 + specs/data/box-normals/box-normals.obj | 26 + .../box-objects-groups-materials.gltf | 486 ++++++++++++ .../box-objects-groups-materials.mtl | 32 + .../box-objects-groups-materials.obj | 133 ++++ .../box-objects-groups/box-objects-groups.mtl | 32 + .../box-objects-groups/box-objects-groups.obj | 135 ++++ specs/data/box-objects/box-objects.mtl | 32 + specs/data/box-objects/box-objects.obj | 132 ++++ .../box-positions-only/box-positions-only.mtl | 12 + .../box-positions-only/box-positions-only.obj | 20 + .../data/box-subdirectories/box-textured.obj | 46 ++ .../materials/box-textured.mtl | 13 + .../materials/images/cesium.png | Bin 0 -> 7665 bytes specs/data/box-textured/box-textured.mtl | 13 + specs/data/box-textured/box-textured.obj | 46 ++ specs/data/box-textured/cesium.png | Bin 0 -> 7665 bytes specs/data/box-triangles/box-triangles.mtl | 12 + specs/data/box-triangles/box-triangles.obj | 46 ++ specs/data/box-uncleaned/box-uncleaned.mtl | 12 + specs/data/box-uncleaned/box-uncleaned.obj | 52 ++ specs/data/box-usemtl/box-usemtl.mtl | 32 + specs/data/box-usemtl/box-usemtl.obj | 129 +++ specs/data/box-uvs/box-uvs.mtl | 12 + specs/data/box-uvs/box-uvs.obj | 40 + specs/data/box/box.gltf | 176 +++++ specs/data/box/box.mtl | 12 + specs/data/box/box.obj | 46 ++ specs/lib/convertSpec.js | 120 ++- specs/lib/gltfSpec.js | 355 +++++++++ specs/lib/imageSpec.js | 91 +++ specs/lib/mtlSpec.js | 53 ++ specs/lib/objSpec.js | 328 ++++++++ 75 files changed, 4650 insertions(+), 952 deletions(-) rename .idea/{OBJ2GLTF.iml => obj2gltf.iml} (100%) create mode 100644 lib/ArrayStorage.js create mode 100644 lib/clone.js create mode 100644 lib/readLines.js delete mode 100644 specs/data/BoxTextured/CesiumLogoFlat.png create mode 100644 specs/data/box-complex-material/alpha.png create mode 100644 specs/data/box-complex-material/ambient.gif create mode 100644 specs/data/box-complex-material/box-complex-material.mtl rename specs/data/{BoxTextured/BoxTextured.obj => box-complex-material/box-complex-material.obj} (73%) create mode 100644 specs/data/box-complex-material/bump.png create mode 100644 specs/data/box-complex-material/diffuse.png create mode 100644 specs/data/box-complex-material/emission.jpg create mode 100644 specs/data/box-complex-material/shininess.png create mode 100644 specs/data/box-complex-material/specular.jpeg create mode 100644 specs/data/box-groups/box-groups.mtl create mode 100644 specs/data/box-groups/box-groups.obj create mode 100644 specs/data/box-missing-mtllib/box-missing-mtllib.obj rename specs/data/{BoxTextured/BoxTextured.mtl => box-missing-texture/box-missing-texture.mtl} (57%) create mode 100644 specs/data/box-missing-texture/box-missing-texture.obj create mode 100644 specs/data/box-mtllib/box-mtllib-blue.mtl create mode 100644 specs/data/box-mtllib/box-mtllib-green.mtl create mode 100644 specs/data/box-mtllib/box-mtllib-red.mtl create mode 100644 specs/data/box-mtllib/box-mtllib.obj create mode 100644 specs/data/box-multiple-materials/box-multiple-materials.mtl create mode 100644 specs/data/box-multiple-materials/box-multiple-materials.obj create mode 100644 specs/data/box-negative-indices/box-negative-indices.mtl create mode 100644 specs/data/box-negative-indices/box-negative-indices.obj create mode 100644 specs/data/box-no-materials/box-no-materials.obj create mode 100644 specs/data/box-normals/box-normals.mtl create mode 100644 specs/data/box-normals/box-normals.obj create mode 100644 specs/data/box-objects-groups-materials/box-objects-groups-materials.gltf create mode 100644 specs/data/box-objects-groups-materials/box-objects-groups-materials.mtl create mode 100644 specs/data/box-objects-groups-materials/box-objects-groups-materials.obj create mode 100644 specs/data/box-objects-groups/box-objects-groups.mtl create mode 100644 specs/data/box-objects-groups/box-objects-groups.obj create mode 100644 specs/data/box-objects/box-objects.mtl create mode 100644 specs/data/box-objects/box-objects.obj create mode 100644 specs/data/box-positions-only/box-positions-only.mtl create mode 100644 specs/data/box-positions-only/box-positions-only.obj create mode 100644 specs/data/box-subdirectories/box-textured.obj create mode 100644 specs/data/box-subdirectories/materials/box-textured.mtl create mode 100644 specs/data/box-subdirectories/materials/images/cesium.png create mode 100644 specs/data/box-textured/box-textured.mtl create mode 100644 specs/data/box-textured/box-textured.obj create mode 100644 specs/data/box-textured/cesium.png create mode 100644 specs/data/box-triangles/box-triangles.mtl create mode 100644 specs/data/box-triangles/box-triangles.obj create mode 100644 specs/data/box-uncleaned/box-uncleaned.mtl create mode 100644 specs/data/box-uncleaned/box-uncleaned.obj create mode 100644 specs/data/box-usemtl/box-usemtl.mtl create mode 100644 specs/data/box-usemtl/box-usemtl.obj create mode 100644 specs/data/box-uvs/box-uvs.mtl create mode 100644 specs/data/box-uvs/box-uvs.obj create mode 100644 specs/data/box/box.gltf create mode 100644 specs/data/box/box.mtl create mode 100644 specs/data/box/box.obj create mode 100644 specs/lib/gltfSpec.js create mode 100644 specs/lib/imageSpec.js create mode 100644 specs/lib/mtlSpec.js create mode 100644 specs/lib/objSpec.js diff --git a/.gitignore b/.gitignore index ad27f7b..b04cad6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,14 +2,16 @@ node_modules npm-debug.log +# TypeScript definitions +typings + # WebStorm user-specific .idea/workspace.xml .idea/tasks.xml -# TypeScript definitions -typings - # Generate data -test coverage +doc +output +test *.tgz diff --git a/.idea/modules.xml b/.idea/modules.xml index eb8cfab..662b990 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/.idea/OBJ2GLTF.iml b/.idea/obj2gltf.iml similarity index 100% rename from .idea/OBJ2GLTF.iml rename to .idea/obj2gltf.iml diff --git a/.npmignore b/.npmignore index 1fe05bd..6e6218a 100644 --- a/.npmignore +++ b/.npmignore @@ -2,6 +2,7 @@ /doc /specs /test +/output /typings /coverage .jshintrc diff --git a/.travis.yml b/.travis.yml index a73b51f..72dada3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,8 @@ node_js: script: - npm run jsHint -- --failTaskOnError - npm run test -- --failTaskOnError --suppressPassed + +after_success: + ## We only need to run coveralls for one node version (doesn't matter which one). + ## We also ignore publishing failures, since restarting an existing travis build would otherwise break. + - if [$(node --version) == v6*]; then npm run coverage && npm run coveralls; fi diff --git a/LICENSE.md b/LICENSE.md index 8938747..82de349 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -11,53 +11,29 @@ Third-Party Code obj2gltf includes the following third-party code. -### async +### bluebird -https://www.npmjs.com/package/async - -> Copyright (c) 2010-2016 Caolan McMahon +> The MIT License (MIT) +> +> Copyright (c) 2013-2015 Petka Antonov > > Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: > > The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +> all copies or substantial portions of the Software. > > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -### byline - -https://www.npmjs.com/package/byline - -> node-byline (C) 2011-2015 John Hewson -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to -deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -IN THE SOFTWARE. +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. ### Cesium @@ -73,6 +49,35 @@ http://cesiumjs.org/ See https://github.com/AnalyticalGraphicsInc/cesium/blob/master/LICENSE.md +### event-stream + +https://www.npmjs.com/package/event-stream + +> The MIT License (MIT) +> +> Copyright (c) 2011 Dominic Tarr +> +> Permission is hereby granted, free of charge, +> to any person obtaining a copy of this software and +> associated documentation files (the "Software"), to +> deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, +> merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom +> the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice +> shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +> ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ### fs-extra https://www.npmjs.com/package/fs-extra @@ -82,16 +87,16 @@ https://www.npmjs.com/package/fs-extra > Copyright (c) 2011-2016 JP Richardson > > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files -(the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +> (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, > merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is > furnished to do so, subject to the following conditions: > > The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - +> > THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +> WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +> OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +> ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ### gltf-pipeline @@ -112,24 +117,24 @@ https://www.npmjs.com/package/yargs The command-line tool uses yargs. > Copyright 2010 James Halliday (mail@substack.net) -Modified work Copyright 2014 Contributors (ben@npmjs.com) +> Modified work Copyright 2014 Contributors (ben@npmjs.com) > > This project is free software released under the MIT/X11 license: > > Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: > > The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +> all copies or substantial portions of the Software. > > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. diff --git a/README.md b/README.md index f1e7784..d9818fc 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Using obj2gltf as a library: var obj2gltf = require('obj2gltf'); var convert = obj2gltf.convert; var options = { - embedImage : false // Don't embed image in the converted glTF + separateTextures : true // Don't embed textures in the converted glTF } convert('model.obj', 'model.gltf', options) .then(function() { @@ -30,20 +30,24 @@ Using obj2gltf as a command-line tool: `node bin/obj2gltf.js -i model.obj -o model.gltf -s` - ## Usage ###Command line flags: |Flag|Description|Required| |----|-----------|--------| -|`-i`|Path to the input OBJ file.| :white_check_mark: Yes| -|`-o`|Directory or filename for the exported glTF file.|No| -|`-b`|Output binary glTF.|No, default `false`| +|`-h`|Display help.|No| +|`-i`|Path to the obj file.| :white_check_mark: Yes| +|`-o`|Path of the converted glTF file.|No| +|`-b`|Save as binary glTF.|No, default `false`| |`-s`|Writes out separate geometry/animation data files, shader files, and textures instead of embedding them in the glTF file.|No, default `false`| |`-t`|Write out separate textures only.|No, default `false`| +|`-c`|Quantize positions, compress texture coordinates, and oct-encode normals.|No, default `false`| +|`-z`|Use the optimization stages in the glTF pipeline.|No, default `false`| +|`-n`|Generate normals if they are missing.|No, default `false`| +|`--cesium`|Optimize the glTF for Cesium by using the sun as a default light source.|No, default `false`| |`--ao`|Apply ambient occlusion to the converted model.|No, default `false`| -|`-h`|Display help|No| +|`--bypassPipeline`|Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension.|No, default `false`| ## Contributions @@ -53,5 +57,5 @@ Pull requests are appreciated. Please use the same [Contributor License Agreeme Developed by the Cesium team.

- +

diff --git a/bin/obj2gltf.js b/bin/obj2gltf.js index caeb576..6cfaf40 100644 --- a/bin/obj2gltf.js +++ b/bin/obj2gltf.js @@ -1,53 +1,116 @@ #!/usr/bin/env node -"use strict"; -var argv = require('yargs').argv; +'use strict'; var Cesium = require('cesium'); -var defined = Cesium.defined; -var defaultValue = Cesium.defaultValue; +var path = require('path'); +var yargs = require('yargs'); var convert = require('../lib/convert'); -if (process.argv.length < 3 || defined(argv.h) || defined(argv.help)) { - console.log('Usage: ./bin/obj2gltf.js [INPUT] [OPTIONS]'); - console.log(' -i, --input Path to obj file'); - console.log(' -o, --output Directory or filename for the exported glTF file'); - console.log(' -b, --binary Output binary glTF'); - console.log(' -s --separate Writes out separate geometry/animation data files, shader files, and textures instead of embedding them in the glTF file.'); - console.log(' -t --separateImage Write out separate textures only.'); - console.log(' -c --compress Quantize positions, compress texture coordinates, and oct-encode normals.'); - console.log(' -h, --help Display this help'); - console.log(' --ao Apply ambient occlusion to the converted model'); - console.log(' --cesium Optimize the glTF for Cesium by using the sun as a default light source.'); - process.exit(0); +var defaultValue = Cesium.defaultValue; +var defined = Cesium.defined; + +var args = process.argv; +args = args.slice(2, args.length); + +var argv = yargs + .usage('Usage: node $0 -i inputPath -o outputPath') + .example('node $0 -i ./specs/data/box/box.obj -o box.gltf') + .help('h') + .alias('h', 'help') + .options({ + 'input': { + alias: 'i', + describe: 'Path to the obj file.', + type: 'string', + normalize: true + }, + 'output': { + alias: 'o', + describe: 'Path of the converted glTF file.', + type: 'string', + normalize: true + }, + 'binary': { + alias: 'b', + describe: 'Save as binary glTF.', + type: 'boolean', + default: false + }, + 'separate': { + alias: 's', + describe: 'Write separate geometry/animation data files, shader files, and textures instead of embedding them in the glTF.', + type: 'boolean', + default: false + }, + 'separateTexture': { + alias: 't', + describe: 'Write out separate textures only.', + type: 'boolean', + default: false + }, + 'compress': { + alias: 'c', + describe: 'Quantize positions, compress texture coordinates, and oct-encode normals.', + type: 'boolean', + default: false + }, + 'optimize': { + alias: 'z', + describe: 'Use the optimization stages in the glTF pipeline.', + type: 'boolean', + default: false + }, + 'cesium': { + describe: 'Optimize the glTF for Cesium by using the sun as a default light source.', + type: 'boolean', + default: false + }, + 'generateNormals': { + alias: 'n', + describe: 'Generate normals if they are missing.', + type: 'boolean', + default: false + }, + 'ao': { + describe: 'Apply ambient occlusion to the converted model.', + type: 'boolean', + default: false + }, + 'bypassPipeline': { + describe: 'Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension.', + type: 'boolean', + default: false + } + }).parse(args); + +var objPath = defaultValue(argv.i, argv._[0]); +var gltfPath = defaultValue(argv.o, argv._[1]); + +if (!defined(objPath)) { + yargs.showHelp(); + return; } -var objFile = defaultValue(argv._[0], defaultValue(argv.i, argv.input)); -var outputPath = defaultValue(argv._[1], defaultValue(argv.o, argv.output)); -var binary = defaultValue(defaultValue(argv.b, argv.binary), false); -var separate = defaultValue(defaultValue(argv.s, argv.separate), false); -var separateImage = defaultValue(defaultValue(argv.t, argv.separateImage), false); -var compress = defaultValue(defaultValue(argv.c, argv.compress), false); -var ao = defaultValue(argv.ao, false); -var optimizeForCesium = defaultValue(argv.cesium, false); - -if (!defined(objFile)) { - throw new Error('-i or --input argument is required. See --help for details.'); +if (!defined(gltfPath)) { + var extension = argv.b ? '.glb' : '.gltf'; + var modelName = path.basename(objPath, path.extname(objPath)); + gltfPath = path.join(path.dirname(objPath), modelName + extension); } +var options = { + binary : argv.b, + separate : argv.s, + separateTextures : argv.t, + compress : argv.c, + optimize : argv.z, + generateNormals : argv.n, + ao : argv.ao, + optimizeForCesium : argv.cesium, + bypassPipeline : argv.bypassPipeline +}; + console.time('Total'); -var options = { - binary : binary, - embed : !separate, - embedImage : !separateImage, - compress : compress, - ao : ao, - optimizeForCesium : optimizeForCesium -}; - -convert(objFile, outputPath, options) +convert(objPath, gltfPath, options) .then(function() { console.timeEnd('Total'); - }) - .catch(function(err) { - console.log(err); }); diff --git a/gulpfile.js b/gulpfile.js index f479227..8d12058 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -6,7 +6,7 @@ var fsExtra = require('fs-extra'); var gulp = require('gulp'); var gulpJshint = require('gulp-jshint'); var Jasmine = require('jasmine'); -var JasmineSpecReporter = require('jasmine-spec-reporter'); +var JasmineSpecReporter = require('jasmine-spec-reporter').SpecReporter; var open = require('open'); var path = require('path'); var yargs = require('yargs'); @@ -20,8 +20,8 @@ var environmentSeparator = process.platform === 'win32' ? ';' : ':'; var nodeBinaries = path.join(__dirname, 'node_modules', '.bin'); process.env.PATH += environmentSeparator + nodeBinaries; -var jsHintFiles = ['**/*.js', '!node_modules/**', '!coverage/**']; -var specFiles = ['**/*.js', '!node_modules/**', '!coverage/**']; +var jsHintFiles = ['**/*.js', '!node_modules/**', '!coverage/**', '!doc/**']; +var specFiles = ['**/*.js', '!node_modules/**', '!coverage/**', '!doc/**', '!bin/**']; gulp.task('jsHint', function () { var stream = gulp.src(jsHintFiles) @@ -53,8 +53,8 @@ gulp.task('test', function (done) { gulp.task('test-watch', function () { gulp.watch(specFiles).on('change', function () { - //We can't simply depend on the test task because Jasmine - //does not like being run multiple times in the same process. + // We can't simply depend on the test task because Jasmine + // does not like being run multiple times in the same process. try { child_process.execSync('jasmine JASMINE_CONFIG_PATH=specs/jasmine.json', { stdio: [process.stdin, process.stdout, process.stderr] @@ -71,7 +71,7 @@ gulp.task('coverage', function () { ' cover' + ' --include-all-sources' + ' --dir coverage' + - ' -x "specs/** coverage/** index.js gulpfile.js"' + + ' -x "specs/**" -x "coverage/**" -x "doc/**" -x "bin/**" -x "index.js" -x "gulpfile.js"' + ' node_modules/jasmine/bin/jasmine.js' + ' JASMINE_CONFIG_PATH=specs/jasmine.json', { stdio: [process.stdin, process.stdout, process.stderr] diff --git a/lib/ArrayStorage.js b/lib/ArrayStorage.js new file mode 100644 index 0000000..c97fd43 --- /dev/null +++ b/lib/ArrayStorage.js @@ -0,0 +1,107 @@ +'use strict'; +var Cesium = require('cesium'); + +var ComponentDatatype = Cesium.ComponentDatatype; + +module.exports = ArrayStorage; + +var initialLength = 1024; // 2^10 +var doublingThreshold = 33554432; // 2^25 (~134 MB for a Float32Array) +var fixedExpansionLength = 33554432; // 2^25 (~134 MB for a Float32Array) + +/** + * Provides expandable typed array storage for geometry data. This is preferable to JS arrays which are + * stored with double precision. The resizing mechanism is similar to std::vector. + * + * @param {ComponentDatatype} componentDatatype The data type. + * @constructor + * + * @private + */ +function ArrayStorage(componentDatatype) { + this.componentDatatype = componentDatatype; + this.typedArray = ComponentDatatype.createTypedArray(componentDatatype, 0); + this.length = 0; +} + +function resize(storage, length) { + var typedArray = ComponentDatatype.createTypedArray(storage.componentDatatype, length); + typedArray.set(storage.typedArray); + storage.typedArray = typedArray; +} + +ArrayStorage.prototype.push = function(value) { + var length = this.length; + var typedArrayLength = this.typedArray.length; + + if (length === 0) { + resize(this, initialLength); + } else if (length === typedArrayLength) { + if (length < doublingThreshold) { + resize(this, typedArrayLength * 2); + } else { + resize(this, typedArrayLength + fixedExpansionLength); + } + } + + this.typedArray[this.length++] = value; +}; + +ArrayStorage.prototype.get = function(index) { + return this.typedArray[index]; +}; + +var sizeOfUint16 = 2; +var sizeOfUint32 = 4; +var sizeOfFloat = 4; + +ArrayStorage.prototype.toUint16Buffer = function() { + var length = this.length; + var typedArray = this.typedArray; + var paddedLength = length + ((length % 2 === 0) ? 0 : 1); // Round to next multiple of 2 + var buffer = Buffer.alloc(paddedLength * sizeOfUint16); + for (var i = 0; i < length; ++i) { + buffer.writeUInt16LE(typedArray[i], i * sizeOfUint16); + } + return buffer; +}; + +ArrayStorage.prototype.toUint32Buffer = function() { + var length = this.length; + var typedArray = this.typedArray; + var buffer = Buffer.alloc(length * sizeOfUint32); + for (var i = 0; i < length; ++i) { + buffer.writeUInt32LE(typedArray[i], i * sizeOfUint32); + } + return buffer; +}; + +ArrayStorage.prototype.toFloatBuffer = function() { + var length = this.length; + var typedArray = this.typedArray; + var buffer = Buffer.alloc(length * sizeOfFloat); + for (var i = 0; i < length; ++i) { + buffer.writeFloatLE(typedArray[i], i * sizeOfFloat); + } + return buffer; +}; + +ArrayStorage.prototype.getMinMax = function(components) { + var length = this.length; + var typedArray = this.typedArray; + var count = length / components; + var min = new Array(components).fill(Number.POSITIVE_INFINITY); + var max = new Array(components).fill(Number.NEGATIVE_INFINITY); + for (var i = 0; i < count; ++i) { + for (var j = 0; j < components; ++j) { + var index = i * components + j; + var value = typedArray[index]; + min[j] = Math.min(min[j], value); + max[j] = Math.max(max[j], value); + } + } + return { + min : min, + max : max + }; +}; diff --git a/lib/clone.js b/lib/clone.js new file mode 100644 index 0000000..d93e059 --- /dev/null +++ b/lib/clone.js @@ -0,0 +1,54 @@ +'use strict'; +var Cesium = require('cesium'); +var ArrayStorage = require('./ArrayStorage'); + +var defaultValue = Cesium.defaultValue; + +module.exports = clone; + +/** + * Clones an object, returning a new object containing the same properties. + * Modified from Cesium.clone to support typed arrays, buffers, and the ArrayStorage class. + * + * @param {Object} object The object to clone. + * @param {Boolean} [deep=false] If true, all properties will be deep cloned recursively. + * @returns {Object} The cloned object. + * + * @private + */ +function clone(object, deep) { + if (object === null || typeof object !== 'object') { + return object; + } + + deep = defaultValue(deep, false); + + var isBuffer = Buffer.isBuffer(object); + var isTypedArray = Object.prototype.toString.call(object.buffer) === '[object ArrayBuffer]'; + var isArrayStorage = object instanceof ArrayStorage; + + var result; + if (isBuffer) { + result = Buffer.from(object); + return result; + } else if (isTypedArray) { + result = object.slice(); + return result; + } else if (isArrayStorage) { + result = new ArrayStorage(object.componentDatatype); + } else { + result = new object.constructor(); + } + + for (var propertyName in object) { + if (object.hasOwnProperty(propertyName)) { + var value = object[propertyName]; + if (deep) { + value = clone(value, deep); + } + result[propertyName] = value; + } + } + + return result; +} diff --git a/lib/convert.js b/lib/convert.js index 8a135d8..6088198 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -1,60 +1,121 @@ -"use strict"; -var path = require('path'); -var GltfPipeline = require('gltf-pipeline').Pipeline; -var parseObj = require('./obj'); -var createGltf = require('./gltf'); +'use strict'; var Cesium = require('cesium'); -var defined = Cesium.defined; +var fsExtra = require('fs-extra'); +var GltfPipeline = require('gltf-pipeline').Pipeline; +var path = require('path'); +var Promise = require('bluebird'); +var createGltf = require('./gltf'); +var loadObj = require('./obj'); + +var fxExtraOutputFile = Promise.promisify(fsExtra.outputFile); +var fsExtraOutputJson = Promise.promisify(fsExtra.outputJson); + var defaultValue = Cesium.defaultValue; +var defined = Cesium.defined; +var DeveloperError = Cesium.DeveloperError; module.exports = convert; -function convert(objFile, outputPath, options) { - options = defaultValue(options, {}); +/** + * Converts an obj file to a glTF file. + * + * @param {String} objPath Path to the obj file. + * @param {String} gltfPath Path of the converted glTF file. + * @param {Object} [options] An object with the following properties: + * @param {Boolean} [options.binary=false] Save as binary glTF. + * @param {Boolean} [options.separate=false] Writes out separate geometry/animation data files, shader files, and textures instead of embedding them in the glTF. + * @param {Boolean} [options.separateTextures=false] Write out separate textures only. + * @param {Boolean} [options.compress=false] Quantize positions, compress texture coordinates, and oct-encode normals. + * @param {Boolean} [options.optimize=false] Use the optimization stages in the glTF pipeline. + * @param {Boolean} [options.optimizeForCesium=false] Optimize the glTF for Cesium by using the sun as a default light source. + * @param {Boolean} [options.generateNormals=false] Generate normals if they are missing. + * @param {Boolean} [options.ao=false] Apply ambient occlusion to the converted model. + * @param {Boolean} [options.textureCompressionOptions] Options sent to the compressTextures stage of gltf-pipeline. + * @param {Boolean} [options.bypassPipeline=false] Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension. + */ + +function convert(objPath, gltfPath, options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); var binary = defaultValue(options.binary, false); - var embed = defaultValue(options.embed, true); - var embedImage = defaultValue(options.embedImage, true); + var separate = defaultValue(options.separate, false); + var separateTextures = defaultValue(options.separateTextures, false); var compress = defaultValue(options.compress, false); - var ao = defaultValue(options.ao, false); + var optimize = defaultValue(options.optimize, false); var optimizeForCesium = defaultValue(options.optimizeForCesium, false); + var generateNormals = defaultValue(options.generateNormals, false); + var ao = defaultValue(options.ao, false); + var textureCompressionOptions = options.textureCompressionOptions; + var bypassPipeline = defaultValue(options.bypassPipeline, false); - if (!defined(objFile)) { - throw new Error('objFile is required'); + if (!defined(objPath)) { + throw new DeveloperError('objPath is required'); } - if (!defined(outputPath)) { - outputPath = path.dirname(objFile); + if (!defined(gltfPath)) { + throw new DeveloperError('gltfPath is required'); } - var inputPath = path.dirname(objFile); - var modelName = path.basename(objFile, '.obj'); - - var extension = path.extname(outputPath); - if (extension !== '') { - modelName = path.basename(outputPath, extension); - outputPath = path.dirname(outputPath); + var basePath = path.dirname(objPath); + var modelName = path.basename(objPath, path.extname(objPath)); + var extension = path.extname(gltfPath); + if (extension === '.glb') { + binary = true; } + gltfPath = path.join(path.dirname(gltfPath), modelName + extension); - extension = binary ? '.glb' : '.gltf'; - var gltfFile = path.join(outputPath, modelName + extension); + var aoOptions = ao ? {} : undefined; - return parseObj(objFile, inputPath) - .then(function(data) { - return createGltf(data, inputPath, modelName); + var pipelineOptions = { + createDirectory : false, + basePath : basePath, + binary : binary, + embed : !separate, + embedImage : !separate && !separateTextures, + quantize : compress, + compressTextureCoordinates : compress, + encodeNormals : compress, + preserve : !optimize, + optimizeForCesium : optimizeForCesium, + smoothNormals : generateNormals, + aoOptions : aoOptions, + textureCompressionOptions : textureCompressionOptions + }; + + return loadObj(objPath) + .then(function(objData) { + return createGltf(objData); }) .then(function(gltf) { - var aoOptions = ao ? {} : undefined; - var options = { - binary: binary, - embed: embed, - embedImage: embedImage, - encodeNormals: compress, - quantize: compress, - aoOptions: aoOptions, - optimizeForCesium : optimizeForCesium, - createDirectory: false, - basePath: inputPath - }; - return GltfPipeline.processJSONToDisk(gltf, gltfFile, options); + return saveExternalBuffer(gltf, gltfPath); + }) + .then(function(gltf) { + if (bypassPipeline) { + return convert._outputJson(gltfPath, gltf); + } else { + return GltfPipeline.processJSONToDisk(gltf, gltfPath, pipelineOptions); + } + }); +} + +/** + * Exposed for testing + * + * @private + */ +convert._outputJson = fsExtraOutputJson; + +function saveExternalBuffer(gltf, gltfPath) { + var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; + if (defined(buffer.uri)) { + return Promise.resolve(gltf); + } + + var bufferName = path.basename(gltfPath, path.extname(gltfPath)); + var bufferUri = bufferName + '.bin'; + buffer.uri = bufferUri; + var bufferPath = path.join(path.dirname(gltfPath), bufferUri); + return fxExtraOutputFile(bufferPath, buffer) + .then(function() { + return gltf; }); } diff --git a/lib/gltf.js b/lib/gltf.js index 4a79303..c0b0189 100644 --- a/lib/gltf.js +++ b/lib/gltf.js @@ -1,148 +1,37 @@ -"use strict"; +'use strict'; var Cesium = require('cesium'); -var Promise = require('bluebird'); -var fs = require('fs-extra'); var path = require('path'); var defined = Cesium.defined; var defaultValue = Cesium.defaultValue; var WebGLConstants = Cesium.WebGLConstants; -var fsWriteFile = Promise.promisify(fs.writeFile); - module.exports = createGltf; -function createGltf(data, inputPath, modelName) { - var vertexCount = data.vertexCount; - var vertexArray = data.vertexArray; - var positionMin = data.positionMin; - var positionMax = data.positionMax; - var hasUVs = data.hasUVs; - var hasNormals = data.hasNormals; - var materialGroups = data.materialGroups; - var materials = data.materials; - var images = data.images; - - var i, j, name; - - var sizeOfFloat32 = 4; - var sizeOfUint32 = 4; - var sizeOfUint16 = 2; - - var indexComponentType; - var indexComponentSize; - - // Reserve the 65535 index for primitive restart - if (vertexCount < 65535) { - indexComponentType = WebGLConstants.UNSIGNED_SHORT; - indexComponentSize = sizeOfUint16; - } else { - indexComponentType = WebGLConstants.UNSIGNED_INT; - indexComponentSize = sizeOfUint32; - } - - // Create primitives - var primitives = []; - var indexArrayLength = 0; - var indexArray; - var indexCount; - for (name in materialGroups) { - if (materialGroups.hasOwnProperty(name)) { - indexArray = materialGroups[name]; - indexCount = indexArray.length; - primitives.push({ - indexArray : indexArray, - indexOffset : indexArrayLength, - indexCount : indexCount, - material : name - }); - indexArrayLength += indexCount; - } - } - - // Create buffer to store vertex and index data - var indexArrayByteLength = indexArrayLength * indexComponentSize; - var vertexArrayLength = vertexArray.length; // In floats - var vertexArrayByteLength = vertexArrayLength * sizeOfFloat32; - var bufferByteLength = vertexArrayByteLength + indexArrayByteLength; - var buffer = new Buffer(bufferByteLength); - - // Write vertex data - var byteOffset = 0; - for (i = 0; i < vertexArrayLength; ++i) { - buffer.writeFloatLE(vertexArray[i], byteOffset); - byteOffset += sizeOfFloat32; - } - - // Write index data - var primitivesLength = primitives.length; - for (i = 0; i < primitivesLength; ++i) { - indexArray = primitives[i].indexArray; - indexCount = indexArray.length; - for (j = 0; j < indexCount; ++j) { - if (indexComponentSize === sizeOfUint16) { - buffer.writeUInt16LE(indexArray[j], byteOffset); - } else { - buffer.writeUInt32LE(indexArray[j], byteOffset); - } - byteOffset += indexComponentSize; - } - } - - var positionByteOffset = 0; - var normalByteOffset = 0; - var uvByteOffset = 0; - var vertexByteStride = 0; - - if (hasNormals && hasUVs) { - normalByteOffset = sizeOfFloat32 * 3; - uvByteOffset = sizeOfFloat32 * 6; - vertexByteStride = sizeOfFloat32 * 8; - } else if (hasNormals && !hasUVs) { - normalByteOffset = sizeOfFloat32 * 3; - vertexByteStride = sizeOfFloat32 * 6; - } else if (!hasNormals && hasUVs) { - uvByteOffset = sizeOfFloat32 * 3; - vertexByteStride = sizeOfFloat32 * 5; - } else if (!hasNormals && !hasUVs) { - vertexByteStride = sizeOfFloat32 * 3; - } - - var bufferId = modelName + '_buffer'; - var bufferViewVertexId = 'bufferView_vertex'; - var bufferViewIndexId = 'bufferView_index'; - var accessorPositionId = 'accessor_position'; - var accessorUVId = 'accessor_uv'; - var accessorNormalId = 'accessor_normal'; - var meshId = 'mesh_' + modelName; - var sceneId = 'scene_' + modelName; - var nodeId = 'node_' + modelName; - var samplerId = 'sampler_0'; - - function getAccessorIndexId(i) { - return 'accessor_index_' + i; - } - - function getMaterialId(material) { - return 'material_' + material; - } - - function getTextureId(image) { - if (!defined(image)) { - return undefined; - } - return 'texture_' + path.basename(image).substr(0, image.lastIndexOf('.')); - } - - function getImageId(image) { - return path.basename(image, path.extname(image)); - } +/** + * Create a glTF from obj data. + * + * @param {Object} objData Output of obj.js, containing an array of nodes containing geometry information, materials, and images. + * @returns {Object} A glTF asset with the KHR_materials_common extension. + * + * @private + */ +function createGltf(objData) { + var nodes = objData.nodes; + var materials = objData.materials; + var images = objData.images; + var sceneId = 'scene'; + var samplerId = 'sampler'; + var bufferId = 'buffer'; + var vertexBufferViewId = 'bufferView_vertex'; + var indexBufferViewId = 'bufferView_index'; var gltf = { accessors : {}, asset : {}, buffers : {}, bufferViews : {}, + extensionsUsed : ['KHR_materials_common'], images : {}, materials : {}, meshes : {}, @@ -154,174 +43,97 @@ function createGltf(data, inputPath, modelName) { }; gltf.asset = { - "generator": "OBJ2GLTF", - "premultipliedAlpha": true, - "profile": { - "api": "WebGL", - "version": "1.0" + generator : 'obj2gltf', + profile : { + api : 'WebGL', + version : '1.0' }, - "version": 1 + version: '1.0' }; gltf.scenes[sceneId] = { - nodes : [nodeId] + nodes : [] }; - gltf.nodes[nodeId] = { - children : [], - matrix : [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], - meshes : [meshId], - name : modelName - }; - - gltf.samplers[samplerId] = {}; // Use default values - - var bufferSeparate = false; - var bufferUri; - if (buffer.length > 201326580) { - // toString fails for buffers larger than ~192MB. Instead save the buffer to a .bin file. - // Source: https://github.com/nodejs/node/issues/4266 - bufferSeparate = true; - bufferUri = modelName + '.bin'; - } else { - bufferUri = 'data:application/octet-stream;base64,' + buffer.toString('base64'); + function getImageId(imagePath) { + return path.basename(imagePath, path.extname(imagePath)); } - gltf.buffers[bufferId] = { - byteLength : bufferByteLength, - type : 'arraybuffer', - uri : bufferUri - }; - - gltf.bufferViews[bufferViewVertexId] = { - buffer : bufferId, - byteLength : vertexArrayByteLength, - byteOffset : 0, - target : WebGLConstants.ARRAY_BUFFER - }; - gltf.bufferViews[bufferViewIndexId] = { - buffer : bufferId, - byteLength : indexArrayByteLength, - byteOffset : vertexArrayByteLength, - target : WebGLConstants.ELEMENT_ARRAY_BUFFER - }; - - for (i = 0; i < primitivesLength; ++i) { - var primitive = primitives[i]; - gltf.accessors[getAccessorIndexId(i)] = { - bufferView : bufferViewIndexId, - byteOffset : primitive.indexOffset * indexComponentSize, - byteStride : 0, - componentType : indexComponentType, - count : primitive.indexCount, - type : 'SCALAR' - }; - } - - gltf.accessors[accessorPositionId] = { - bufferView : bufferViewVertexId, - byteOffset : positionByteOffset, - byteStride : vertexByteStride, - componentType : WebGLConstants.FLOAT, - count : vertexCount, - min : positionMin, - max : positionMax, - type : 'VEC3' - }; - - if (hasNormals) { - gltf.accessors[accessorNormalId] = { - bufferView : bufferViewVertexId, - byteOffset : normalByteOffset, - byteStride : vertexByteStride, - componentType : WebGLConstants.FLOAT, - count : vertexCount, - type : 'VEC3' - }; - } - - if (hasUVs) { - gltf.accessors[accessorUVId] = { - bufferView : bufferViewVertexId, - byteOffset : uvByteOffset, - byteStride : vertexByteStride, - componentType : WebGLConstants.FLOAT, - count : vertexCount, - type : 'VEC2' - }; - } - - var gltfPrimitives = []; - gltf.meshes[meshId] = { - name : modelName, - primitives : gltfPrimitives - }; - - var gltfAttributes = {}; - gltfAttributes.POSITION = accessorPositionId; - if (hasNormals) { - gltfAttributes.NORMAL = accessorNormalId; - } - if (hasUVs) { - gltfAttributes.TEXCOORD_0 = accessorUVId; - } - - for (i = 0; i < primitivesLength; ++i) { - gltfPrimitives.push({ - attributes : gltfAttributes, - indices : getAccessorIndexId(i), - material : getMaterialId(primitives[i].material), - mode : WebGLConstants.TRIANGLES - }); - } - - for (name in materials) { - if (materials.hasOwnProperty(name)) { - var material = materials[name]; - var materialId = getMaterialId(name); - var values = { - ambient : defaultValue(defaultValue(getTextureId(material.ambientColorMap), material.ambientColor), [0, 0, 0, 1]), - diffuse : defaultValue(defaultValue(getTextureId(material.diffuseColorMap), material.diffuseColor), [0, 0, 0, 1]), - emission : defaultValue(defaultValue(getTextureId(material.emissionColorMap), material.emissionColor), [0, 0, 0, 1]), - specular : defaultValue(defaultValue(getTextureId(material.specularColorMap), material.specularColor), [0, 0, 0, 1]), - shininess : defaultValue(material.specularShininess, 0.0) - }; - - gltf.materials[materialId] = { - name: name, - values: values - }; + function getTextureId(imagePath) { + if (!defined(imagePath) || !defined(images[imagePath])) { + return undefined; } + return 'texture_' + getImageId(imagePath); } - for (name in images) { - if (images.hasOwnProperty(name)) { - var image = images[name]; - var imageId = getImageId(name); - var textureId = getTextureId(name); - var format; - var channels = image.channels; - switch (channels) { - case 1: - format = WebGLConstants.ALPHA; - break; - case 2: - format = WebGLConstants.LUMINANCE_ALPHA; - break; - case 3: - format = WebGLConstants.RGB; - break; - case 4: - format = WebGLConstants.RGBA; - break; + function createMaterial(material, hasNormals) { + var ambient = defaultValue(defaultValue(getTextureId(material.ambientColorMap), material.ambientColor), [0, 0, 0, 1]); + var diffuse = defaultValue(defaultValue(getTextureId(material.diffuseColorMap), material.diffuseColor), [0.5, 0.5, 0.5, 1]); + var emission = defaultValue(defaultValue(getTextureId(material.emissionColorMap), material.emissionColor), [0, 0, 0, 1]); + var specular = defaultValue(defaultValue(getTextureId(material.specularColorMap), material.specularColor), [0, 0, 0, 1]); + var alpha = defaultValue(defaultValue(material.alpha), 1.0); + var shininess = defaultValue(material.specularShininess, 0.0); + var hasSpecular = (shininess > 0.0) && (specular[0] > 0.0 || specular[1] > 0.0 || specular[2] > 0.0); + + var transparent; + var transparency = 1.0; + if (typeof diffuse === 'string') { + transparency = alpha; + transparent = images[material.diffuseColorMap].transparent || (transparency < 1.0); + } else { + diffuse[3] = alpha; + transparent = diffuse[3] < 1.0; + } + + var doubleSided = transparent; + + if (!hasNormals) { + // Constant technique only factors in ambient and emission sources - set emission to diffuse + emission = diffuse; + } + + var technique = hasNormals ? (hasSpecular ? 'PHONG' : 'LAMBERT') : 'CONSTANT'; + return { + extensions : { + KHR_materials_common : { + technique : technique, + values : { + ambient : ambient, + diffuse : diffuse, + emission : emission, + specular : specular, + shininess : shininess, + transparency : transparency, + transparent : transparent, + doubleSided : doubleSided + } + } } + }; + } + + if (Object.keys(images).length > 0) { + gltf.samplers[samplerId] = { + magFilter : WebGLConstants.LINEAR, + minFilter : WebGLConstants.LINEAR, + wrapS : WebGLConstants.REPEAT, + wrapT : WebGLConstants.REPEAT + }; + } + + for (var imagePath in images) { + if (images.hasOwnProperty(imagePath)) { + var image = images[imagePath]; + var imageId = getImageId(imagePath); + var textureId = getTextureId(imagePath); gltf.images[imageId] = { + name : imageId, uri : image.uri }; gltf.textures[textureId] = { - format : format, - internalFormat : format, + format : image.format, + internalFormat : image.format, sampler : samplerId, source : imageId, target : WebGLConstants.TEXTURE_2D, @@ -330,9 +142,191 @@ function createGltf(data, inputPath, modelName) { } } - if (bufferSeparate) { - var bufferPath = path.join(inputPath, modelName + '.bin'); - return fsWriteFile(bufferPath, buffer); + var vertexBuffers = []; + var vertexByteOffset = 0; + var indexBuffers = []; + var indexBuffersByteOffset = 0; + var accessorCount = 0; + + function addVertexAttribute(array, components) { + var count = array.length / components; + var buffer = array.toFloatBuffer(); + var minMax = array.getMinMax(components); + + var type = (components === 3 ? 'VEC3' : 'VEC2'); + var accessor = { + bufferView : vertexBufferViewId, + byteOffset : vertexByteOffset, + byteStride : 0, + componentType : WebGLConstants.FLOAT, + count : count, + min : minMax.min, + max : minMax.max, + type : type + }; + + vertexByteOffset += buffer.length; + vertexBuffers.push(buffer); + var accessorId = 'accessor_' + accessorCount++; + gltf.accessors[accessorId] = accessor; + return accessorId; } + + function addIndexArray(array, uint32Indices) { + var buffer = uint32Indices ? array.toUint32Buffer() : array.toUint16Buffer(); + var componentType = uint32Indices ? WebGLConstants.UNSIGNED_INT : WebGLConstants.UNSIGNED_SHORT; + var length = array.length; + var minMax = array.getMinMax(1); + var accessor = { + bufferView : indexBufferViewId, + byteOffset : indexBuffersByteOffset, + byteStride : 0, + componentType : componentType, + count : length, + min : minMax.min, + max : minMax.max, + type : 'SCALAR' + }; + + indexBuffersByteOffset += buffer.length; + indexBuffers.push(buffer); + + var accessorId = 'accessor_' + accessorCount++; + gltf.accessors[accessorId] = accessor; + return accessorId; + } + + function requiresUint32Indices(nodes) { + var nodesLength = nodes.length; + for (var i = 0; i < nodesLength; ++i) { + var meshes = nodes[i].meshes; + var meshesLength = meshes.length; + for (var j = 0; j < meshesLength; ++j) { + // Reserve the 65535 index for primitive restart + var vertexCount = meshes[j].positions.length / 3; + if (vertexCount > 65534) { + return true; + } + } + } + return false; + } + + var uint32Indices = requiresUint32Indices(nodes); + var gltfSceneNodes = gltf.scenes[sceneId].nodes; + var nodesLength = nodes.length; + for (var i = 0; i < nodesLength; ++i) { + // Add node + var node = nodes[i]; + var nodeId = node.name; + gltfSceneNodes.push(nodeId); + var gltfNodeMeshes = []; + gltf.nodes[nodeId] = { + name : nodeId, + meshes : gltfNodeMeshes + }; + + // Add meshes to node + var meshes = node.meshes; + var meshesLength = meshes.length; + for (var j = 0; j < meshesLength; ++j) { + var mesh = meshes[j]; + var meshId = mesh.name; + gltfNodeMeshes.push(meshId); + + var hasPositions = mesh.positions.length > 0; + var hasNormals = mesh.normals.length > 0; + var hasUVs = mesh.uvs.length > 0; + + var attributes = {}; + if (hasPositions) { + attributes.POSITION = addVertexAttribute(mesh.positions, 3); + } + if (hasNormals) { + attributes.NORMAL = addVertexAttribute(mesh.normals, 3); + } + if (hasUVs) { + attributes.TEXCOORD_0 = addVertexAttribute(mesh.uvs, 2); + } + + // Unload resources + mesh.positions = undefined; + mesh.normals = undefined; + mesh.uvs = undefined; + + var gltfMeshPrimitives = []; + gltf.meshes[meshId] = { + name : meshId, + primitives : gltfMeshPrimitives + }; + + // Add primitives to mesh + var primitives = mesh.primitives; + var primitivesLength = primitives.length; + for (var k = 0; k < primitivesLength; ++k) { + var primitive = primitives[k]; + var indexAccessorId = addIndexArray(primitive.indices, uint32Indices); + primitive.indices = undefined; // Unload resources + var materialId = primitive.material; + + if (!defined(materialId)) { + // Create a default material if the primitive does not specify one + materialId = 'default'; + } + + var material = defaultValue(materials[materialId], {}); + var gltfMaterial = gltf.materials[materialId]; + if (defined(gltfMaterial)) { + // Check if this material has already been added but with incompatible shading + var normalShading = (gltfMaterial.extensions.KHR_materials_common.technique !== 'CONSTANT'); + if (hasNormals !== normalShading) { + materialId += (hasNormals ? '_shaded' : '_constant'); + gltfMaterial = gltf.materials[materialId]; + } + } + + if (!defined(gltfMaterial)) { + gltf.materials[materialId] = createMaterial(material, hasNormals); + } + + gltfMeshPrimitives.push({ + attributes : attributes, + indices : indexAccessorId, + material : materialId, + mode : WebGLConstants.TRIANGLES + }); + } + } + } + + var vertexBuffer = Buffer.concat(vertexBuffers); + var indexBuffer = Buffer.concat(indexBuffers); + var buffer = Buffer.concat([vertexBuffer, indexBuffer]); + + // Buffers larger than ~192MB cannot be base64 encoded due to a NodeJS limitation. Instead the buffer will be saved to a .bin file. Source: https://github.com/nodejs/node/issues/4266 + var bufferUri; + if (buffer.length <= 201326580) { + bufferUri = 'data:application/octet-stream;base64,' + buffer.toString('base64'); + } + + gltf.buffers[bufferId] = { + byteLength : buffer.byteLength, + uri : bufferUri + }; + + gltf.bufferViews[vertexBufferViewId] = { + buffer : bufferId, + byteLength : vertexBuffer.length, + byteOffset : 0, + target : WebGLConstants.ARRAY_BUFFER + }; + + gltf.bufferViews[indexBufferViewId] = { + buffer : bufferId, + byteLength : indexBuffer.length, + byteOffset : vertexBuffer.length, + target : WebGLConstants.ELEMENT_ARRAY_BUFFER + }; + return gltf; } diff --git a/lib/image.js b/lib/image.js index 0be148b..9d2ad74 100644 --- a/lib/image.js +++ b/lib/image.js @@ -1,12 +1,55 @@ -"use strict"; -var Promise = require('bluebird'); +'use strict'; +var Cesium = require('cesium'); var fs = require('fs-extra'); var path = require('path'); +var Promise = require('bluebird'); var fsReadFile = Promise.promisify(fs.readFile); +var WebGLConstants = Cesium.WebGLConstants; + module.exports = loadImage; +/** + * Load an image file and get information about it. + * + * @param {String} imagePath Path to the image file. + * @returns {Promise} A promise resolving to the image information, or undefined if the file doesn't exist. + * + * @private + */ +function loadImage(imagePath) { + return fsReadFile(imagePath) + .then(function(data) { + var extension = path.extname(imagePath); + var uriType = getUriType(extension); + var uri = uriType + ';base64,' + data.toString('base64'); + + var info = { + transparent : false, + channels : 3, + data : data, + uri : uri, + format : getFormat(3) + }; + + if (extension === '.png') { + // Color type is encoded in the 25th bit of the png + var colorType = data[25]; + var channels = getChannels(colorType); + info.channels = channels; + info.transparent = (channels === 4); + info.format = getFormat(channels); + } + + return info; + }) + .catch(function() { + console.log('Could not read image file at ' + imagePath + '. Material will ignore this image.'); + return undefined; + }); +} + function getChannels(colorType) { switch (colorType) { case 0: // greyscale @@ -24,40 +67,27 @@ function getChannels(colorType) { function getUriType(extension) { switch (extension) { - case 'png': + case '.png': return 'data:image/png'; - case 'jpg': + case '.jpg': + case '.jpeg': return 'data:image/jpeg'; - case 'jpeg': - return 'data:image/jpeg'; - case 'gif': + case '.gif': return 'data:image/gif'; default: - return 'data:image/' + extension; + return 'data:image/' + extension.slice(1); } } -function loadImage(imagePath) { - return fsReadFile(imagePath) - .then(function(data) { - var extension = path.extname(imagePath).slice(1); - var uriType = getUriType(extension); - var uri = uriType + ';base64,' + data.toString('base64'); - - var info = { - transparent: false, - channels: 3, - data: data, - uri: uri - }; - - if (path.extname(imagePath) === 'png') { - // Color type is encoded in the 25th bit of the png - var colorType = data[25]; - var channels = getChannels(colorType); - info.channels = channels; - info.transparent = (channels === 4); - } - return info; - }); +function getFormat(channels) { + switch (channels) { + case 1: + return WebGLConstants.ALPHA; + case 2: + return WebGLConstants.LUMINANCE_ALPHA; + case 3: + return WebGLConstants.RGB; + case 4: + return WebGLConstants.RGBA; + } } diff --git a/lib/mtl.js b/lib/mtl.js index 941be7f..50a93fe 100644 --- a/lib/mtl.js +++ b/lib/mtl.js @@ -1,114 +1,105 @@ -"use strict"; -var Promise = require('bluebird'); -var fs = require('fs-extra'); -var defined = require('cesium').defined; +'use strict'; +var path = require('path'); +var readLines = require('./readLines'); -var fsReadFile = Promise.promisify(fs.readFile); +module.exports = loadMtl; -module.exports = { - getDefault : getDefault, - parse : parse -}; - -function createMaterial() { - return { - ambientColor : undefined, // Ka - emissionColor : undefined, // Ke - diffuseColor : undefined, // Kd - specularColor : undefined, // Ks - specularShininess : undefined, // Ns - alpha : undefined, // d / Tr - ambientColorMap : undefined, // map_Ka - emissionColorMap : undefined, // map_Ke - diffuseColorMap : undefined, // map_Kd - specularColorMap : undefined, // map_Ks - specularShininessMap : undefined, // map_Ns - normalMap : undefined, // map_Bump - alphaMap : undefined // map_d - }; +function Material() { + this.ambientColor = undefined; // Ka + this.emissionColor = undefined; // Ke + this.diffuseColor = undefined; // Kd + this.specularColor = undefined; // Ks + this.specularShininess = undefined; // Ns + this.alpha = undefined; // d / Tr + this.ambientColorMap = undefined; // map_Ka + this.emissionColorMap = undefined; // map_Ke + this.diffuseColorMap = undefined; // map_Kd + this.specularColorMap = undefined; // map_Ks + this.specularShininessMap = undefined; // map_Ns + this.normalMap = undefined; // map_Bump + this.alphaMap = undefined; // map_d } -function getDefault() { - var material = createMaterial(); - material.diffuseColor = [0.5, 0.5, 0.5, 1.0]; - return material; -} +/** + * Parse an mtl file. + * + * @param {String} mtlPath Path to the mtl file. + * @returns {Promise} A promise resolving to the materials, or an empty object if the mtl file doesn't exist. + * + * @private + */ +function loadMtl(mtlPath) { + var material; + var values; + var value; + var materials = {}; -function parse(mtlPath) { - return fsReadFile(mtlPath, 'utf8') - .then(function (contents) { - var materials = {}; - var material; - var values; - var value; - var lines = contents.split('\n'); - var length = lines.length; - for (var i = 0; i < length; ++i) { - var line = lines[i].trim(); - if (/^newmtl /i.test(line)) { - var name = line.substring(7).trim(); - material = createMaterial(); - materials[name] = material; - } else if (/^Ka /i.test(line)) { - values = line.substring(3).trim().split(' '); - material.ambientColor = [ - parseFloat(values[0]), - parseFloat(values[1]), - parseFloat(values[2]), - 1.0 - ]; - } else if (/^Ke /i.test(line)) { - values = line.substring(3).trim().split(' '); - material.emissionColor = [ - parseFloat(values[0]), - parseFloat(values[1]), - parseFloat(values[2]), - 1.0 - ]; - } else if (/^Kd /i.test(line)) { - values = line.substring(3).trim().split(' '); - material.diffuseColor = [ - parseFloat(values[0]), - parseFloat(values[1]), - parseFloat(values[2]), - 1.0 - ]; - } else if (/^Ks /i.test(line)) { - values = line.substring(3).trim().split(' '); - material.specularColor = [ - parseFloat(values[0]), - parseFloat(values[1]), - parseFloat(values[2]), - 1.0 - ]; - } else if (/^Ns /i.test(line)) { - value = line.substring(3).trim(); - material.specularShininess = parseFloat(value); - } else if (/^d /i.test(line)) { - value = line.substring(2).trim(); - material.alpha = parseFloat(value); - } else if (/^Tr /i.test(line)) { - value = line.substring(3).trim(); - material.alpha = parseFloat(value); - } else if (/^map_Ka /i.test(line)) { - material.ambientColorMap = line.substring(7).trim(); - } else if (/^map_Ke /i.test(line)) { - material.emissionColorMap = line.substring(7).trim(); - } else if (/^map_Kd /i.test(line)) { - material.diffuseColorMap = line.substring(7).trim(); - } else if (/^map_Ks /i.test(line)) { - material.specularColorMap = line.substring(7).trim(); - } else if (/^map_Ns /i.test(line)) { - material.specularShininessMap = line.substring(7).trim(); - } else if (/^map_Bump /i.test(line)) { - material.normalMap = line.substring(9).trim(); - } else if (/^map_d /i.test(line)) { - material.alphaMap = line.substring(6).trim(); - } - } - if (defined(material.alpha)) { - material.diffuseColor[3] = material.alpha; - } + function parseLine(line) { + line = line.trim(); + if (/^newmtl /i.test(line)) { + var name = line.substring(7).trim(); + material = new Material(); + materials[name] = material; + } else if (/^Ka /i.test(line)) { + values = line.substring(3).trim().split(' '); + material.ambientColor = [ + parseFloat(values[0]), + parseFloat(values[1]), + parseFloat(values[2]), + 1.0 + ]; + } else if (/^Ke /i.test(line)) { + values = line.substring(3).trim().split(' '); + material.emissionColor = [ + parseFloat(values[0]), + parseFloat(values[1]), + parseFloat(values[2]), + 1.0 + ]; + } else if (/^Kd /i.test(line)) { + values = line.substring(3).trim().split(' '); + material.diffuseColor = [ + parseFloat(values[0]), + parseFloat(values[1]), + parseFloat(values[2]), + 1.0 + ]; + } else if (/^Ks /i.test(line)) { + values = line.substring(3).trim().split(' '); + material.specularColor = [ + parseFloat(values[0]), + parseFloat(values[1]), + parseFloat(values[2]), + 1.0 + ]; + } else if (/^Ns /i.test(line)) { + value = line.substring(3).trim(); + material.specularShininess = parseFloat(value); + } else if (/^d /i.test(line)) { + value = line.substring(2).trim(); + material.alpha = parseFloat(value); + } else if (/^Tr /i.test(line)) { + value = line.substring(3).trim(); + material.alpha = parseFloat(value); + } else if (/^map_Ka /i.test(line)) { + material.ambientColorMap = getAbsolutePath(line.substring(7).trim(), mtlPath); + } else if (/^map_Ke /i.test(line)) { + material.emissionColorMap = getAbsolutePath(line.substring(7).trim(), mtlPath); + } else if (/^map_Kd /i.test(line)) { + material.diffuseColorMap = getAbsolutePath(line.substring(7).trim(), mtlPath); + } else if (/^map_Ks /i.test(line)) { + material.specularColorMap = getAbsolutePath(line.substring(7).trim(), mtlPath); + } else if (/^map_Ns /i.test(line)) { + material.specularShininessMap = getAbsolutePath(line.substring(7).trim(), mtlPath); + } else if (/^map_Bump /i.test(line)) { + material.normalMap = getAbsolutePath(line.substring(9).trim(), mtlPath); + } else if (/^map_d /i.test(line)) { + material.alphaMap = getAbsolutePath(line.substring(6).trim(), mtlPath); + } + } + + return readLines(mtlPath, parseLine) + .then(function() { return materials; }) .catch(function() { @@ -116,3 +107,10 @@ function parse(mtlPath) { return {}; }); } + +function getAbsolutePath(imagePath, mtlPath) { + if (!path.isAbsolute(imagePath)) { + imagePath = path.join(path.dirname(mtlPath), imagePath); + } + return imagePath; +} diff --git a/lib/obj.js b/lib/obj.js index b60e569..abe024d 100644 --- a/lib/obj.js +++ b/lib/obj.js @@ -1,365 +1,447 @@ -"use strict"; - +'use strict'; var Cesium = require('cesium'); -var Promise = require('bluebird'); -var byline = require('byline'); -var fs = require('fs-extra'); var path = require('path'); +var Promise = require('bluebird'); +var ArrayStorage = require('./ArrayStorage'); var loadImage = require('./image'); -var Material = require('./mtl'); +var loadMtl = require('./mtl'); +var readLines = require('./readLines'); -var Cartesian3 = Cesium.Cartesian3; +var combine = Cesium.combine; +var ComponentDatatype = Cesium.ComponentDatatype; +var defaultValue = Cesium.defaultValue; var defined = Cesium.defined; +var RuntimeError = Cesium.RuntimeError; -module.exports = parseObj; +module.exports = loadObj; -// OBJ regex patterns are from ThreeJS (https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/OBJLoader.js) +// Object name (o) -> node +// Group name (g) -> mesh +// Material name (usemtl) -> primitive -function parseObj(objFile, inputPath) { - return getObjInfo(objFile, inputPath) - .then(function(result) { - var info = result.info; - var materials = result.materials; - var images = result.images; - return processObj(objFile, info, materials, images); +function Node() { + this.name = undefined; + this.meshes = []; +} + +function Mesh() { + this.name = undefined; + this.primitives = []; + this.positions = new ArrayStorage(ComponentDatatype.FLOAT); + this.normals = new ArrayStorage(ComponentDatatype.FLOAT); + this.uvs = new ArrayStorage(ComponentDatatype.FLOAT); +} + +function Primitive() { + this.material = undefined; + this.indices = new ArrayStorage(ComponentDatatype.UNSIGNED_INT); +} + +// OBJ regex patterns are modified from ThreeJS (https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/OBJLoader.js) +var vertexPattern = /v( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // v float float float +var normalPattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // vn float float float +var uvPattern = /vt( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // vt float float +var facePattern1 = /f( +-?\d+)\/?( +-?\d+)\/?( +-?\d+)\/?( +-?\d+)?\/?/; // f vertex vertex vertex ... +var facePattern2 = /f( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)?/; // f vertex/uv vertex/uv vertex/uv ... +var facePattern3 = /f( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))?/; // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ... +var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/; // f vertex//normal vertex//normal vertex//normal ... + +/** + * Parse an obj file. + * + * @param {String} objPath Path to the obj file. + * @returns {Promise} A promise resolving to the obj data. + * @exception {RuntimeError} The file does not have any geometry information in it. + * + * @private + */ +function loadObj(objPath) { + // Global store of vertex attributes listed in the obj file + var positions = new ArrayStorage(ComponentDatatype.FLOAT); + var normals = new ArrayStorage(ComponentDatatype.FLOAT); + var uvs = new ArrayStorage(ComponentDatatype.FLOAT); + + // The current node, mesh, and primitive + var node; + var mesh; + var primitive; + + // All nodes seen in the obj + var nodes = []; + + // Used to build the indices. The vertex cache is unique to each mesh. + var vertexCache = {}; + var vertexCacheLimit = 1000000; + var vertexCacheCount = 0; + var vertexCount = 0; + + // All mtl paths seen in the obj + var mtlPaths = []; + + function getName(name) { + return (name === '' ? undefined : name); + } + + function addNode(name) { + node = new Node(); + node.name = getName(name); + nodes.push(node); + addMesh(); + } + + function addMesh(name) { + mesh = new Mesh(); + mesh.name = getName(name); + node.meshes.push(mesh); + addPrimitive(); + + // Clear the vertex cache for each new mesh + vertexCache = {}; + vertexCacheCount = 0; + vertexCount = 0; + } + + function addPrimitive() { + primitive = new Primitive(); + mesh.primitives.push(primitive); + } + + function useMaterial(name) { + // Look to see if this material has already been used by a primitive in the mesh + var material = getName(name); + var primitives = mesh.primitives; + var primitivesLength = primitives.length; + for (var i = 0; i < primitivesLength; ++i) { + primitive = primitives[i]; // Sets the active primitive in case of returning early + if (primitive.material === material) { + return; + } + } + // Add a new primitive with this material + addPrimitive(); + primitive.material = getName(name); + } + + function getOffset(a, attributeData, components) { + var i = parseInt(a); + if (i < 0) { + // Negative vertex indexes reference the vertices immediately above it + return (attributeData.length / components + i) * components; + } + return (i - 1) * components; + } + + function createVertex(p, u, n) { + // Positions + if (defined(p)) { + var pi = getOffset(p, positions, 3); + var px = positions.get(pi + 0); + var py = positions.get(pi + 1); + var pz = positions.get(pi + 2); + mesh.positions.push(px); + mesh.positions.push(py); + mesh.positions.push(pz); + } + + // Normals + if (defined(n)) { + var ni = getOffset(n, normals, 3); + var nx = normals.get(ni + 0); + var ny = normals.get(ni + 1); + var nz = normals.get(ni + 2); + mesh.normals.push(nx); + mesh.normals.push(ny); + mesh.normals.push(nz); + } + + // UVs + if (defined(u)) { + var ui = getOffset(u, uvs, 2); + var ux = uvs.get(ui + 0); + var uy = uvs.get(ui + 1); + mesh.uvs.push(ux); + mesh.uvs.push(uy); + } + } + + function addVertex(v, p, u, n) { + var index = vertexCache[v]; + if (!defined(index)) { + index = vertexCount++; + vertexCache[v] = index; + createVertex(p, u, n); + + // Prevent the vertex cache from growing too large. As a result of clearing the cache there + // may be some duplicate vertices. + vertexCacheCount++; + if (vertexCacheCount > vertexCacheLimit) { + vertexCacheCount = 0; + vertexCache = {}; + } + } + return index; + } + + function addFace(v1, p1, u1, n1, v2, p2, u2, n2, v3, p3, u3, n3, v4, p4, u4, n4) { + var index1 = addVertex(v1, p1, u1, n1); + var index2 = addVertex(v2, p2, u2, n2); + var index3 = addVertex(v3, p3, u3, n3); + + primitive.indices.push(index1); + primitive.indices.push(index2); + primitive.indices.push(index3); + + // Triangulate if the face is a quad + if (defined(v4)) { + var index4 = addVertex(v4, p4, u4, n4); + primitive.indices.push(index1); + primitive.indices.push(index3); + primitive.indices.push(index4); + } + } + + function parseLine(line) { + line = line.trim(); + var result; + + if ((line.length === 0) || (line.charAt(0) === '#')) { + // Don't process empty lines or comments + } else if (/^o\s/i.test(line)) { + var objectName = line.substring(2).trim(); + addNode(objectName); + } else if (/^g\s/i.test(line)) { + var groupName = line.substring(2).trim(); + addMesh(groupName); + } else if (/^usemtl\s/i.test(line)) { + var materialName = line.substring(7).trim(); + useMaterial(materialName); + } else if (/^mtllib/i.test(line)) { + var paths = line.substring(7).trim().split(' '); + mtlPaths = mtlPaths.concat(paths); + } else if ((result = vertexPattern.exec(line)) !== null) { + positions.push(parseFloat(result[1])); + positions.push(parseFloat(result[2])); + positions.push(parseFloat(result[3])); + } else if ((result = normalPattern.exec(line) ) !== null) { + normals.push(parseFloat(result[1])); + normals.push(parseFloat(result[2])); + normals.push(parseFloat(result[3])); + } else if ((result = uvPattern.exec(line)) !== null) { + uvs.push(parseFloat(result[1])); + uvs.push(1.0 - parseFloat(result[2])); // Flip y so 0.0 is the bottom of the image + } else if ((result = facePattern1.exec(line)) !== null) { + addFace( + result[1], result[1], undefined, undefined, + result[2], result[2], undefined, undefined, + result[3], result[3], undefined, undefined, + result[4], result[4], undefined, undefined + ); + } else if ((result = facePattern2.exec(line)) !== null) { + addFace( + result[1], result[2], result[3], undefined, + result[4], result[5], result[6], undefined, + result[7], result[8], result[9], undefined, + result[10], result[11], result[12], undefined + ); + } else if ((result = facePattern3.exec(line)) !== null) { + addFace( + result[1], result[2], result[3], result[4], + result[5], result[6], result[7], result[8], + result[9], result[10], result[11], result[12], + result[13], result[14], result[15], result[16] + ); + } else if ((result = facePattern4.exec(line)) !== null) { + addFace( + result[1], result[2], undefined, result[3], + result[4], result[5], undefined, result[6], + result[7], result[8], undefined, result[9], + result[10], result[11], undefined, result[12] + ); + } + } + + // Create a default node in case there are no o/g/usemtl lines in the obj + addNode(); + + // Parse the obj file + return readLines(objPath, parseLine) + .then(function() { + // Unload resources + positions = undefined; + normals = undefined; + uvs = undefined; + + // Load materials and images + return finishLoading(nodes, mtlPaths, objPath); }); } -function processObj(objFile, info, materials, images) { - return new Promise(function(resolve) { - // A vertex is specified by indexes into each of the attribute arrays, - // but these indexes may be different. This maps the separate indexes to a single index. - var vertexCache = {}; - var vertexCount = 0; - - var vertexArray = []; - - var positions = []; - var normals = []; - var uvs = []; - - var positionMin = [Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE]; - var positionMax = [-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE]; - - var hasNormals = info.hasNormals; - var hasUVs = info.hasUVs; - - var materialGroups = {}; // Map material to index array - var currentIndexArray; - - // Switch to the material-specific index array, or create it if it doesn't exist - function useMaterial(material) { - if (!defined(materials[material])) { - useDefaultMaterial(); - } else { - currentIndexArray = materialGroups[material]; - if (!defined(currentIndexArray)) { - currentIndexArray = []; - materialGroups[material] = currentIndexArray; - } - } - } - - function useDefaultMaterial() { - var defaultMaterial = 'czmDefaultMat'; - if (!defined(materials[defaultMaterial])) { - materials[defaultMaterial] = Material.getDefault(); - } - useMaterial(defaultMaterial); - } - - var materialsLength = Object.keys(materials).length; - if (materialsLength === 0) { - useDefaultMaterial(); - } - - function getOffset(a, data, components) { - var i = parseInt(a); - if (i < 0) { - // Negative vertex indexes reference the vertices immediately above it - return (data.length / components + i) * components; - } - return (i - 1) * components; - } - - function createVertex(p, u, n) { - // Positions - var pi = getOffset(p, positions, 3); - var px = positions[pi + 0]; - var py = positions[pi + 1]; - var pz = positions[pi + 2]; - - positionMin[0] = Math.min(px, positionMin[0]); - positionMin[1] = Math.min(py, positionMin[1]); - positionMin[2] = Math.min(pz, positionMin[2]); - positionMax[0] = Math.max(px, positionMax[0]); - positionMax[1] = Math.max(py, positionMax[1]); - positionMax[2] = Math.max(pz, positionMax[2]); - vertexArray.push(px, py, pz); - - // Normals - if (hasNormals) { - var ni = getOffset(n, normals, 3); - var nx = normals[ni + 0]; - var ny = normals[ni + 1]; - var nz = normals[ni + 2]; - vertexArray.push(nx, ny, nz); - } - - // UVs - if (hasUVs) { - if (defined(u)) { - var ui = getOffset(u, uvs, 2); - var ux = uvs[ui + 0]; - var uy = uvs[ui + 1]; - // Flip y so 0.0 is the bottom of the image - uy = 1.0 - uy; - vertexArray.push(ux, uy); - } else { - // Some objects in the model may not have uvs, fill with 0's for consistency - vertexArray.push(0.0, 0.0); - } - } - } - - function addVertex(v, p, u, n) { - var index = vertexCache[v]; - if (!defined(index)) { - index = vertexCount++; - vertexCache[v] = index; - createVertex(p, u, n); - } - - return index; - } - - function addFace(v1, p1, u1, n1, v2, p2, u2, n2, v3, p3, u3, n3, v4, p4, u4, n4) { - var index1 = addVertex(v1, p1, u1, n1); - var index2 = addVertex(v2, p2, u2, n2); - var index3 = addVertex(v3, p3, u3, n3); - - currentIndexArray.push(index1); - currentIndexArray.push(index2); - currentIndexArray.push(index3); - - // Triangulate if the face is a quad - if (defined(v4)) { - var index4 = addVertex(v4, p4, u4, n4); - currentIndexArray.push(index1); - currentIndexArray.push(index3); - currentIndexArray.push(index4); - } - } - - // v float float float - var vertexPattern = /v( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; - - // vn float float float - var normalPattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; - - // vt float float - var uvPattern = /vt( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; - - // f vertex vertex vertex ... - var facePattern1 = /f( +-?\d+)\/?( +-?\d+)\/?( +-?\d+)\/?( +-?\d+)?\/?/; - - // f vertex/uv vertex/uv vertex/uv ... - var facePattern2 = /f( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)?/; - - // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ... - var facePattern3 = /f( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))?/; - - // f vertex//normal vertex//normal vertex//normal ... - var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/; - - var stream = byline(fs.createReadStream(objFile, {encoding: 'utf8'})); - stream.on('data', function (line) { - line = line.trim(); - var result; - if ((line.length === 0) || (line.charAt(0) === '#')) { - // Don't process empty lines or comments - } else if ((result = vertexPattern.exec(line)) !== null) { - positions.push( - parseFloat(result[1]), - parseFloat(result[2]), - parseFloat(result[3]) - ); - } else if ((result = normalPattern.exec(line) ) !== null) { - var nx = parseFloat(result[1]); - var ny = parseFloat(result[2]); - var nz = parseFloat(result[3]); - var normal = Cartesian3.normalize(new Cartesian3(nx, ny, nz), new Cartesian3()); - normals.push(normal.x, normal.y, normal.z); - } else if ((result = uvPattern.exec(line)) !== null) { - uvs.push( - parseFloat(result[1]), - parseFloat(result[2]) - ); - } else if ((result = facePattern1.exec(line)) !== null) { - addFace( - result[1], result[1], undefined, undefined, - result[2], result[2], undefined, undefined, - result[3], result[3], undefined, undefined, - result[4], result[4], undefined, undefined - ); - } else if ((result = facePattern2.exec(line)) !== null) { - addFace( - result[1], result[2], result[3], undefined, - result[4], result[5], result[6], undefined, - result[7], result[8], result[9], undefined, - result[10], result[11], result[12], undefined - ); - } else if ((result = facePattern3.exec(line)) !== null) { - addFace( - result[1], result[2], result[3], result[4], - result[5], result[6], result[7], result[8], - result[9], result[10], result[11], result[12], - result[13], result[14], result[15], result[16] - ); - } else if ((result = facePattern4.exec(line)) !== null) { - addFace( - result[1], result[2], undefined, result[3], - result[4], result[5], undefined, result[6], - result[7], result[8], undefined, result[9], - result[10], result[11], undefined, result[12] - ); - } else if (/^usemtl /.test(line)) { - var materialName = line.substring(7).trim(); - useMaterial(materialName); - } +function finishLoading(nodes, mtlPaths, objPath) { + nodes = cleanNodes(nodes); + if (nodes.length === 0) { + throw new RuntimeError(objPath + ' does not have any geometry data'); + } + return loadMaterials(mtlPaths, objPath) + .then(function(materials) { + var imagePaths = getImagePaths(materials); + return loadImages(imagePaths, objPath) + .then(function(images) { + return { + nodes : nodes, + materials : materials, + images : images + }; + }); }); +} - stream.on('end', function () { - resolve({ - vertexCount: vertexCount, - vertexArray: vertexArray, - positionMin: positionMin, - positionMax: positionMax, - hasUVs: hasUVs, - hasNormals: hasNormals, - materialGroups: materialGroups, - materials: materials, - images: images +function getAbsolutePath(mtlPath, objPath) { + if (!path.isAbsolute(mtlPath)) { + mtlPath = path.join(path.dirname(objPath), mtlPath); + } + return mtlPath; +} + +function loadMaterials(mtlPaths, objPath) { + var materials = {}; + return Promise.map(mtlPaths, function(mtlPath) { + mtlPath = getAbsolutePath(mtlPath, objPath); + return loadMtl(mtlPath) + .then(function(materialsInMtl) { + materials = combine(materials, materialsInMtl); }); - }); + }).then(function() { + return materials; }); } -function getImages(inputPath, materials) { - // Collect all the image files from the materials - var images = []; +function loadImages(imagePaths) { + var images = {}; + return Promise.map(imagePaths, function(imagePath) { + return loadImage(imagePath) + .then(function(image) { + if (defined(image)) { + images[imagePath] = image; + } + }); + }).then(function() { + return images; + }); +} + +function getImagePaths(materials) { + var imagePaths = []; for (var name in materials) { if (materials.hasOwnProperty(name)) { var material = materials[name]; - if (defined(material.ambientColorMap) && (images.indexOf(material.ambientColorMap) === -1)) { - images.push(material.ambientColorMap); + if (defined(material.ambientColorMap) && imagePaths.indexOf(material.ambientColorMap) === -1) { + imagePaths.push(material.ambientColorMap); } - if (defined(material.diffuseColorMap) && (images.indexOf(material.diffuseColorMap) === -1)) { - images.push(material.diffuseColorMap); + if (defined(material.diffuseColorMap) && imagePaths.indexOf(material.diffuseColorMap) === -1) { + imagePaths.push(material.diffuseColorMap); } - if (defined(material.emissionColorMap) && (images.indexOf(material.emissionColorMap) === -1)) { - images.push(material.emissionColorMap); + if (defined(material.emissionColorMap) && imagePaths.indexOf(material.emissionColorMap) === -1) { + imagePaths.push(material.emissionColorMap); } - if (defined(material.specularColorMap) && (images.indexOf(material.specularColorMap) === -1)) { - images.push(material.specularColorMap); + if (defined(material.specularColorMap) && imagePaths.indexOf(material.specularColorMap) === -1) { + imagePaths.push(material.specularColorMap); } } } + return imagePaths; +} - // Load the image files - var promises = []; - var imagesInfo = {}; - var imagesLength = images.length; - for (var i = 0; i < imagesLength; i++) { - var imagePath = images[i]; - if (!path.isAbsolute(imagePath)) { - imagePath = path.join(inputPath, imagePath); +function removeEmptyPrimitives(primitives) { + var final = []; + var primitivesLength = primitives.length; + for (var i = 0; i < primitivesLength; ++i) { + var primitive = primitives[i]; + if (primitive.indices.length > 0) { + final.push(primitive); } - promises.push(loadImage(imagePath)); } - return Promise.all(promises) - .then(function(imageInfoArray) { - var imageInfoArrayLength = imageInfoArray.length; - for (var j = 0; j < imageInfoArrayLength; j++) { - var image = images[j]; - var imageInfo = imageInfoArray[j]; - imagesInfo[image] = imageInfo; - } - return imagesInfo; - }); + return final; } -function getMaterials(mtlPath, hasMaterialGroups) { - if (hasMaterialGroups && defined(mtlPath)) { - return Material.parse(mtlPath); +function removeEmptyMeshes(meshes) { + var final = []; + var meshesLength = meshes.length; + for (var i = 0; i < meshesLength; ++i) { + var mesh = meshes[i]; + mesh.primitives = removeEmptyPrimitives(mesh.primitives); + if ((mesh.primitives.length > 0) && (mesh.positions.length > 0)) { + final.push(mesh); + } } - - return {}; + return final; } -function getObjInfo(objFile, inputPath) { - var mtlPath; - var materials; - var info; - var hasMaterialGroups = false; - var hasPositions = false; - var hasNormals = false; - var hasUVs = false; - return new Promise(function(resolve, reject) { - var stream = byline(fs.createReadStream(objFile, {encoding: 'utf8'})); - stream.on('data', function (line) { - if (!defined(mtlPath)) { - var mtllibMatches = line.match(/^mtllib.*/gm); - if (mtllibMatches !== null) { - var mtlFile = mtllibMatches[0].substring(7).trim(); - mtlPath = mtlFile; - if (!path.isAbsolute(mtlPath)) { - mtlPath = path.join(inputPath, mtlFile); - } - } - } - if (!hasMaterialGroups) { - hasMaterialGroups = /^usemtl/gm.test(line); - } - if (!hasPositions) { - hasPositions = /^v\s/gm.test(line); - } - if (!hasNormals) { - hasNormals = /^vn/gm.test(line); - } - if (!hasUVs) { - hasUVs = /^vt/gm.test(line); - } - }); - - stream.on('error', function(err) { - reject(err); - }); - - stream.on('end', function () { - if (!hasPositions) { - reject(new Error('Could not process OBJ file, no positions.')); - } - info = { - hasNormals: hasNormals, - hasUVs: hasUVs - }; - resolve(); - }); - }) - .then(function() { - return getMaterials(mtlPath, hasMaterialGroups); - }) - .then(function(returnedMaterials) { - materials = returnedMaterials; - return getImages(inputPath, materials); - }) - .then(function(images) { - return { - info : info, - materials : materials, - images : images - }; - }); +function meshesHaveNames(meshes) { + var meshesLength = meshes.length; + for (var i = 0; i < meshesLength; ++i) { + if (defined(meshes[i].name)) { + return true; + } + } + return false; +} + +function removeEmptyNodes(nodes) { + var final = []; + var nodesLength = nodes.length; + for (var i = 0; i < nodesLength; ++i) { + var node = nodes[i]; + var meshes = removeEmptyMeshes(node.meshes); + if (meshes.length === 0) { + continue; + } + node.meshes = meshes; + if (!defined(node.name) && meshesHaveNames(meshes)) { + // If the obj has groups (g) but not object groups (o) then convert meshes to nodes + var meshesLength = meshes.length; + for (var j = 0; j < meshesLength; ++j) { + var mesh = meshes[j]; + var convertedNode = new Node(); + convertedNode.name = mesh.name; + convertedNode.meshes = [mesh]; + final.push(convertedNode); + } + } else { + final.push(node); + } + } + return final; +} + +function setDefaultNames(items, defaultName, usedNames) { + var itemsLength = items.length; + for (var i = 0; i < itemsLength; ++i) { + var item = items[i]; + var name = defaultValue(item.name, defaultName); + var occurrences = usedNames[name]; + if (defined(occurrences)) { + usedNames[name]++; + name = name + '_' + occurrences; + } else { + usedNames[name] = 1; + } + item.name = name; + } +} + +function setDefaults(nodes) { + var usedNames = {}; + setDefaultNames(nodes, 'Node', usedNames); + var nodesLength = nodes.length; + for (var i = 0; i < nodesLength; ++i) { + var node = nodes[i]; + setDefaultNames(node.meshes, node.name + '-Mesh', usedNames); + } +} + +function cleanNodes(nodes) { + nodes = removeEmptyNodes(nodes); + setDefaults(nodes); + return nodes; } diff --git a/lib/readLines.js b/lib/readLines.js new file mode 100644 index 0000000..4178147 --- /dev/null +++ b/lib/readLines.js @@ -0,0 +1,27 @@ +'use strict'; +var eventStream = require('event-stream'); +var fsExtra = require('fs-extra'); +var Promise = require('bluebird'); + +module.exports = readLines; + +/** + * Read a file line-by-line. + * + * @param {String} path Path to the file. + * @param {Function} callback Function to call when reading each line. + * @returns {Promise} A promise when the reader is finished. + * + * @private + */ +function readLines(path, callback) { + return new Promise(function(resolve, reject) { + fsExtra.createReadStream(path) + .on('error', reject) + .on('end', resolve) + .pipe(eventStream.split()) + .pipe(eventStream.mapSync(function (line) { + callback(line); + })); + }); +} diff --git a/package.json b/package.json index 75a2b70..67cd831 100644 --- a/package.json +++ b/package.json @@ -6,45 +6,44 @@ "contributors": [ { "name": "Analytical Graphics, Inc., and Contributors", - "url": "https://github.com/AnalyticalGraphicsInc/OBJ2GLTF/graphs/contributors" + "url": "https://github.com/AnalyticalGraphicsInc/obj2gltf/graphs/contributors" } ], "keywords": [ "obj", "gltf" ], - "homepage": "https://github.com/AnalyticalGraphicsInc/OBJ2GLTF", + "homepage": "https://github.com/AnalyticalGraphicsInc/obj2gltf", "repository": { "type": "git", - "url": "git@github.com:AnalyticalGraphicsInc/OBJ2GLTF.git" + "url": "git@github.com:AnalyticalGraphicsInc/obj2gltf.git" }, "bugs": { - "url": "https://github.com/AnalyticalGraphicsInc/OBJ2GLTF/issues" + "url": "https://github.com/AnalyticalGraphicsInc/obj2gltf/issues" }, "main": "index.js", "engines": { "node": ">=4.0.0" }, "dependencies": { - "async": "2.1.2", - "bluebird": "3.4.6", - "byline": "5.0.0", - "cesium": "1.26.0", - "fs-extra": "0.30.0", - "gltf-pipeline": "0.1.0-alpha8", - "yargs": "6.3.0" + "bluebird": "^3.4.7", + "cesium": "^1.31.0", + "event-stream": "^3.3.4", + "fs-extra": "^2.0.0", + "gltf-pipeline": "^0.1.0-alpha11", + "yargs": "^7.0.1" }, "devDependencies": { - "gulp": "3.9.1", - "gulp-jshint": "2.0.2", - "istanbul": "0.4.5", - "jasmine": "2.5.2", - "jasmine-spec-reporter": "2.7.0", - "jshint": "2.9.4", - "jshint-stylish": "2.2.1", - "open": "0.0.5", - "requirejs": "2.3.2", - "typings": "1.4.0" + "gulp": "^3.9.1", + "gulp-jshint": "^2.0.4", + "istanbul": "^0.4.5", + "jasmine": "^2.5.3", + "jasmine-spec-reporter": "^3.2.0", + "jshint": "^2.9.4", + "jshint-stylish": "^2.2.1", + "open": "^0.0.5", + "requirejs": "^2.3.3", + "typings": "^2.1.0" }, "scripts": { "prepublish": "typings install", diff --git a/specs/data/BoxTextured/CesiumLogoFlat.png b/specs/data/BoxTextured/CesiumLogoFlat.png deleted file mode 100644 index 88bada374836fcecc80ef666ab093f61a42e7e0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22051 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 BWBULA diff --git a/specs/data/box-complex-material/alpha.png b/specs/data/box-complex-material/alpha.png new file mode 100644 index 0000000000000000000000000000000000000000..8437c62e20eaafd489a2e1b2fda6784706d37591 GIT binary patch literal 1843 zcmV-32h8}1P)gi#>8|958DebJ}g;GOtxjIEORk2EaOd4 zvgoFen6QP14GK=j46IaW(P|1>x^DICv48HlxA(U9&-vZn@2lFJkH2$I&po~O9E6M* zF=E7s5&B|M^usG`4kyDfbq>4Dx(g-au_v4Bp-?Ei8B1VzrYeT*=xi7G3v)BGOQ9D~ zN^fTR#&)o%?2Pp>X6ixq-f=83uCXdNJQ(NuEFV zeS)Y_B+w33kHe1UcP@CKqc zo~c4iwP8eaoRI?h-Vaoh1e(WhI@w9Lsq>`dzUSOPv}b=U&D;0N{)1{lHb2d=4y zukX#bfHf@~A)A0sA~;it%)aLWOv-G0rs)`D(=oE?1Z2|*;`L;D3E70qm{6xzL6b6? zpJ6%#+jNLzYJqKPA(=X0n>t9Q@d^JfY06lBsACiOjZ6|yO#@tLN(kWCql*ZZ?>3BaTILW11)&^K^RnN80!-Gpn( zY`X5B#ZFy@O1hll>wD-ssHUuj>;D~;IMAuY)vO;da(;jM0;Vau*(LDqUBVBM;`}fBOP7(uHl5r%t-u6C*eYi!{mEg0w{Tm{|g~ z)^eA-w|c4U*))F#4(Se;R62X0-M<2bbcI0D<(`7%{mY;K(+z_3>5oWsopiZJX9C3Q z{5?bx@AI&?0Q?v=Mcy8^wDu+rpQ4>~itmX-f&I&;>E0N7=3OM+q{ltkBOaPb9{%Td zh0Y9F-g}f<(w71&#wB+aMUvWmsoI6!#1MW5B}nPCuUqiMx;$s_ti(Q`ko58Q#r^wB zn7Ulb)8xG8bXk6-?Et9Xj{aMuNsnibp)Ja`a&qNe&=>LU-9`gVq=x+W3u4|Ue8|#6 zV)GwQDDEA}d(>>H(<0&+NwH_mQnH5LB&$mgfvR|pfaMtaxJIJmj0C7pj5vFX&iG`Ku%Ghi;iui+nm8+-4#RM2>@9nl^$(>Jywj3?I;g|pb002ovPDHLkV1i;#~fNk%w1VbcK90O$Vzj;w2st!$94ZIQ2TlCW=+v2c>Icbc(rm9ue^vv`)Y za+I`rnzMPEw0fMid!4p?p0|CUxPG9yf1$d7pSy;px`&{=hoQZQsJw}yzKN;5ile`Z zrNE1-zl*EDjH$qqs=<@3!HuiJl&->#tizSB!;h`RmaoN^u*R6S#+jMN=(5O~xW}5B z$mzDop}EMLo5||4%A2>zqPoePoXYFC%A=jj?4Hc+y33`b%;%uZ?!e2Yq|NBN&8DHw z@4w8grOxTR&Zn%*>cGve!_BCs&+EO9W%A!qly{(&@(2v#izdv(xam)9S?3y2sSCvDNFi z)a%97yU5kGwbk>+*1WmZ?a9`*%+CV@`yx8!-*y_*N zz`fb=&Dy)c+3U{P$I;orzS{D_+U(ET$iUn4!`to9+sVh<=g-}}&E3P++R4P+?#SHe z!QJ%N+se`2zR}&v#oh4F+v?Tb#mU|2&)>z^+{@D5%gWyC+1D%Jh%;fgj;_BSv*xcge&E@yt;@{)q+1=&M+T-ip=$(;ne8$-Raih=IP|-eu4u>gDI;;Op4t z=;hYy_vGp9=jz($>E_q$_}=RH*zNh~?Aze%`r7XL>FwO=?CIg{{N(NR=kDX^@8sp~ z_v`NJ>G0*}@A&QS>*(GAsP^XKaG`|b4U@$>NO^!)Di>hkpQ_4MoP_5JVm>+|*U z_Vw)T_W$?x?ezEa^7r}k`1|(w^z`}s_xbkq`u+I&_xAh#`TO|#{Q39&|NH&=`2PR- z|Ns2|`~Ls@A^s6Va%Ew3Wn>_CX>@2HM@dak04x9i007ef(*OVn{s5B+97wRB!Gj1B zDqP60p~Hs|BTAe|v7*I`7&B_z$g!ixk03*e97(dI$&)Bks$9vkrOTHvW6GRKv!>0P zICJXUsW6zTOMddW33CMq5FR{eD7}FcNR}>P>I|Y(su$0zIeT#$#0H8D6=1`P9ZQz1 zjU!?5Sh9+>ZAxRR2897qwyxc~V|9e#qspz{kGd9x`HHu&;kz)p40`pqF-28%=pLR- z*)EVes*E|_oKRI9$(2Kk9%~~`U(TF^4FWy7wQChX_~=6IS5+7quXE>a5#}^)otmWR z9zGn%Bh9~Q8e2`g`S2Tjf+df+h==p*!)*+bK7HjQ*4V@TyS8z>`^i#J#H$~E#J72n zw!*BRKRnFr_Z^MpfkD52ZWwuRhaX0&9O2)AxdGwcfD#!KM+*o>_*x#wF$mEs;7O=q zWk=9r;X{7#x8aDQwSY*4A0E_87bLbgniTh?Sdc0ix@cozIkX}pL5qlZrCmCB*xf5Pd}(G~H;DP0DukWs zrdT$p$r+n()+rVY3;so?oqVc*=Vq_8*{7W_==hf{Wez$h4RT)9OO=Ycd4nx&86#Yz zkscx7PV!-j=@lsA6o;pz!m*Pjq^7z_POspWs;B-iG%8atkiKds5Sz->sjZ}jQIjUT z+PcV0wg?(*f_rq*$sXxN*n+GpS$eE)Cc^X2wb;gkiI4q35tAR&CYTPkH)`s_*{3{tMtq`RW&Fwea6>Qn1=SG1G}zpYIJFu>m+_{d7ZB#Lll zR4lx(z4PI#QXLbEwu{98KZ76}DcQnp$A?)9vb_342yICFqI_5j{H#2$#ND2xip-VW zVDr2KV+V>!S@2953_r`OLxezt`cWxHS5{BbMLI@OA^L|4Zq9`Lx zH{D#b3o#fOPa+hssJyT~&cM;V-a2$5&cgCwgtLobeB<>zlGvTmpx_LfU1VoBf&w_w zhOK$*A!;{JM$)Qbkv!O_1+ZYCyt=V8l*zieGPbJt$%z4Z)y?T(lTJI_Ig65pf?AF~EP5ONrP(nm_T4p01-BO$jJ5->6%TKdmI>PK^8{Xlg^O$u52|rv z0Hf7FXT!r>=R()G@N9$EJWC>LPz@gvpe=A8(pTqP1GC}9E<6KJ+N9mX2P5i)*Ir;; z*FGex&XF(GW}se!Hb5R!6Nhf+vxRW3j1kziuS4)l94>eb!TmKT3A}a-32BjP*|;uk z8T$}EIOoDi%W#AtdIqm~@jzgtnmr<*UKT*a!_7G_K3P0sg9ZSdNfX$CP~o)|^f#>! zc|>zW(c>C#Gy%{2SveSooiG>}WPgb8RUZPo+<@W97K^fxHyQ%3?IMd)OByx=5MiP| zByYL_@&3=oyk(Mp!E19^PYe8)G#CL#5J$DnvI=`~b8w~(0bG*9Iu)=F7s4*lW z;!Vo%m}-dzG)WV{4yvVt$+-!G*XUqmghHgybs+)uz5F7QFVr=Ct61Kcj~Oj2r=pz_>(o4)(BfeN$dgO~=H^Ak|E;We`XN z6s%Z>R&H(!l>_^rnlg0bgH|Vv8Ok&=8X7cU zFxmUo`F8h9pTO(av;d9bYyhAuF%eL!;NVn~hr$KE)LHzqn3TDN*F+#`UrdC%laY4P z{zyRasusW;mY(5Z&_=a0NcsU35%Ofl-SU!qH3RWZ8Z<=H$fOk`;6_FPMQAaZp8pIP zK)3lzjgZdDOp$8tNPwxwKoJ=l=IKd$ht!qs)nF!#A0RJGimCQcQ-`7=BqYn&lU8=F zn|vHzgRKe%nzS$EngAA&++A!S;v(y~ z7C+)qj?Mq9_Noso0m7lddGvnvAe#cW9e@#Uq*3|7m+S-t@5q(q!rd~+2p6yZAN|qR zp!y(F$2GYfQ;Y!khT`u&Z8HIWfbC-g?k<2voR7kMU_kw}9Y8kPFR!<{+XWkOLq3Lo zwB-Pm#(&ph2o!e+8+tEMSF6vk#0wV@ihv zApwHevJbgX10yJgZ59HI;Dif94O8HGN+$rg5E9jZ4Z!dTQ!oNqNQeI1762L01BSp0 z(GU*VfDEZ{1}HFbl($zQ;R|<$h@2>J0U(1Rp$45eip;hGC836-n2IA70IpCHcc6-} z7-0Zt68aR2wkTNwPzxtv2e;UZUKIo>@d3U#jFZNNC$S1JNQ}zJOiYLpBejgs2mlwr z5}Fu|$`}A=#}ayAjl{SEFF|eGc#9BVj4Xi*3J{LA$bc}>2j~ckB?uEZ(2k@y0p$1+ zzK~w>NQVPp3N;Z5$(WBf_ysrN1pqmP9Ci~jP>?D}k2(Q-3aNet(26>N3(NM9!50AP zxD%l;hZf0o&A1bHAcY;tZA9P~XfTpo=VE`M1t?i^B(NDs(EgI{<^lES7x5^QriKAE z`498MFK~;8EPPsN_kT%i63|Hk5Rc)PVga+V2M|G zQv&b?G9nB2*Of1o0FkgG$>0k;NtOnc0iK4I$>0T08I_m_mjlrVo5+^2v<7x*5We67 zdI4p9absf+|*1hB2+PM+FfCe7G zoHl0xQ9zjES&m|00@X=*0}ujQ5Sr~76O~W}Ai$Bi762H~1AQQ#_o))TPzYmC0~f%b z>Jj01yxW6cW?-yAPXTXqBxqPI=Z7g+M_=Dqd*#@LOP^GTBIlu5CA*Uq8VHO literal 0 HcmV?d00001 diff --git a/specs/data/box-complex-material/box-complex-material.mtl b/specs/data/box-complex-material/box-complex-material.mtl new file mode 100644 index 0000000..3f69a9e --- /dev/null +++ b/specs/data/box-complex-material/box-complex-material.mtl @@ -0,0 +1,20 @@ +# Blender MTL File: 'None' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.200000 0.200000 0.200000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.100000 0.100000 0.100000 +Ni 1.000000 +d 0.900000 +Tr 0.900000 +map_Ka ambient.gif +map_Ke emission.jpg +map_Kd diffuse.png +map_Ks specular.jpeg +map_Ns shininess.png +map_Bump bump.png +map_d alpha.png +illum 2 diff --git a/specs/data/BoxTextured/BoxTextured.obj b/specs/data/box-complex-material/box-complex-material.obj similarity index 73% rename from specs/data/BoxTextured/BoxTextured.obj rename to specs/data/box-complex-material/box-complex-material.obj index 64da02d..0ca30b0 100644 --- a/specs/data/BoxTextured/BoxTextured.obj +++ b/specs/data/box-complex-material/box-complex-material.obj @@ -1,7 +1,7 @@ -# Blender v2.77 (sub 0) OBJ File: 'BoxTextured.blend' +# Blender v2.78 (sub 0) OBJ File: '' # www.blender.org -mtllib BoxTextured.mtl -o Cube_Cube.001 +mtllib box-complex-material.mtl +o Cube v -1.000000 -1.000000 1.000000 v -1.000000 1.000000 1.000000 v -1.000000 -1.000000 -1.000000 @@ -26,9 +26,9 @@ vt 0.0000 0.0000 vt 1.0000 0.0000 vt 1.0000 1.0000 vt 0.0000 1.0000 -vt 0.0000 0.0000 vt 1.0000 0.0000 vt 1.0000 1.0000 +vt 0.0000 0.0000 vt 0.0000 1.0000 vn -1.0000 0.0000 0.0000 vn 0.0000 0.0000 -1.0000 @@ -36,11 +36,11 @@ vn 1.0000 0.0000 0.0000 vn 0.0000 0.0000 1.0000 vn 0.0000 -1.0000 0.0000 vn 0.0000 1.0000 0.0000 -usemtl Textured +usemtl Material s off -f 2/1/1 4/2/1 3/3/1 1/4/1 -f 4/5/2 8/6/2 7/7/2 3/8/2 -f 8/9/3 6/10/3 5/11/3 7/12/3 -f 6/13/4 2/14/4 1/15/4 5/16/4 -f 1/17/5 3/18/5 7/7/5 5/16/5 -f 6/13/6 8/6/6 4/19/6 2/20/6 +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/data/box-complex-material/bump.png b/specs/data/box-complex-material/bump.png new file mode 100644 index 0000000000000000000000000000000000000000..fcc5ba8ca8f1cee6a086ab43efa054578e6a670a GIT binary patch literal 4720 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 Hu0mjfJUwN5 literal 0 HcmV?d00001 diff --git a/specs/data/box-complex-material/emission.jpg b/specs/data/box-complex-material/emission.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e5c85a943f6c5c95ca46b1bfd5fc9e8d7f1c4b77 GIT binary patch literal 7092 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 literal 0 HcmV?d00001 diff --git a/specs/data/box-complex-material/shininess.png b/specs/data/box-complex-material/shininess.png new file mode 100644 index 0000000000000000000000000000000000000000..bfcf1a2c625c38a8da19b7c7390102dde2b3f90a GIT binary patch literal 4094 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 literal 0 HcmV?d00001 diff --git a/specs/data/box-complex-material/specular.jpeg b/specs/data/box-complex-material/specular.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..661bf98060289f196f149b5c0b6b05ff737f9ff8 GIT binary patch literal 5618 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 IJS9*353{Fhng9R* literal 0 HcmV?d00001 diff --git a/specs/data/box-groups/box-groups.mtl b/specs/data/box-groups/box-groups.mtl new file mode 100644 index 0000000..2f9b11a --- /dev/null +++ b/specs/data/box-groups/box-groups.mtl @@ -0,0 +1,32 @@ +# Blender MTL File: 'box-objects.blend' +# Material Count: 3 + +newmtl Blue +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.000000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Green +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.640000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Red +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.000000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-groups/box-groups.obj b/specs/data/box-groups/box-groups.obj new file mode 100644 index 0000000..5ac69f0 --- /dev/null +++ b/specs/data/box-groups/box-groups.obj @@ -0,0 +1,132 @@ +# Blender v2.78 (sub 0) OBJ File: 'box-objects.blend' +# www.blender.org +mtllib box-groups.mtl +g CubeBlue +v -1.000000 -1.000000 -4.000000 +v -1.000000 1.000000 -4.000000 +v -1.000000 -1.000000 -6.000000 +v -1.000000 1.000000 -6.000000 +v 1.000000 -1.000000 -4.000000 +v 1.000000 1.000000 -4.000000 +v 1.000000 -1.000000 -6.000000 +v 1.000000 1.000000 -6.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Blue +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 +g CubeGreen +v 4.000000 -1.000000 1.000000 +v 4.000000 1.000000 1.000000 +v 4.000000 -1.000000 -1.000000 +v 4.000000 1.000000 -1.000000 +v 6.000000 -1.000000 1.000000 +v 6.000000 1.000000 1.000000 +v 6.000000 -1.000000 -1.000000 +v 6.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Green +s off +f 9/21/7 10/22/7 12/23/7 11/24/7 +f 11/25/8 12/26/8 16/27/8 15/28/8 +f 15/29/9 16/30/9 14/31/9 13/32/9 +f 13/33/10 14/34/10 10/35/10 9/36/10 +f 11/25/11 15/37/11 13/38/11 9/36/11 +f 16/39/12 12/26/12 10/35/12 14/40/12 +g CubeRed +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Red +s off +f 17/41/13 18/42/13 20/43/13 19/44/13 +f 19/45/14 20/46/14 24/47/14 23/48/14 +f 23/49/15 24/50/15 22/51/15 21/52/15 +f 21/53/16 22/54/16 18/55/16 17/56/16 +f 19/45/17 23/57/17 21/58/17 17/56/17 +f 24/59/18 20/46/18 18/55/18 22/60/18 diff --git a/specs/data/box-missing-mtllib/box-missing-mtllib.obj b/specs/data/box-missing-mtllib/box-missing-mtllib.obj new file mode 100644 index 0000000..5246261 --- /dev/null +++ b/specs/data/box-missing-mtllib/box-missing-mtllib.obj @@ -0,0 +1,46 @@ +# Blender v2.78 (sub 0) OBJ File: '' +# www.blender.org +mtllib box.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Material +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/data/BoxTextured/BoxTextured.mtl b/specs/data/box-missing-texture/box-missing-texture.mtl similarity index 57% rename from specs/data/BoxTextured/BoxTextured.mtl rename to specs/data/box-missing-texture/box-missing-texture.mtl index eeff817..c5c879f 100644 --- a/specs/data/BoxTextured/BoxTextured.mtl +++ b/specs/data/box-missing-texture/box-missing-texture.mtl @@ -1,13 +1,13 @@ -# Blender MTL File: 'BoxTextured.blend' +# Blender MTL File: 'box.blend' # Material Count: 1 -newmtl Textured +newmtl Material Ns 96.078431 -Ka 1.000000 1.000000 1.000000 +Ka 0.000000 0.000000 0.000000 Kd 0.640000 0.640000 0.640000 Ks 0.500000 0.500000 0.500000 Ke 0.000000 0.000000 0.000000 Ni 1.000000 d 1.000000 illum 2 -map_Kd CesiumLogoFlat.png +map_Kd cesium.png diff --git a/specs/data/box-missing-texture/box-missing-texture.obj b/specs/data/box-missing-texture/box-missing-texture.obj new file mode 100644 index 0000000..ced632f --- /dev/null +++ b/specs/data/box-missing-texture/box-missing-texture.obj @@ -0,0 +1,46 @@ +# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# www.blender.org +mtllib box-missing-texture.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Material +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/data/box-mtllib/box-mtllib-blue.mtl b/specs/data/box-mtllib/box-mtllib-blue.mtl new file mode 100644 index 0000000..d3fe863 --- /dev/null +++ b/specs/data/box-mtllib/box-mtllib-blue.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'box-multiple-materials.blend' +# Material Count: 1 + +newmtl Blue +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.000000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-mtllib/box-mtllib-green.mtl b/specs/data/box-mtllib/box-mtllib-green.mtl new file mode 100644 index 0000000..89abba9 --- /dev/null +++ b/specs/data/box-mtllib/box-mtllib-green.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'box-multiple-materials.blend' +# Material Count: 1 + +newmtl Green +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.640000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-mtllib/box-mtllib-red.mtl b/specs/data/box-mtllib/box-mtllib-red.mtl new file mode 100644 index 0000000..3721d86 --- /dev/null +++ b/specs/data/box-mtllib/box-mtllib-red.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'box-multiple-materials.blend' +# Material Count: 1 + +newmtl Red +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.000000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-mtllib/box-mtllib.obj b/specs/data/box-mtllib/box-mtllib.obj new file mode 100644 index 0000000..cced063 --- /dev/null +++ b/specs/data/box-mtllib/box-mtllib.obj @@ -0,0 +1,50 @@ +# Blender v2.78 (sub 0) OBJ File: 'box-multiple-materials.blend' +# www.blender.org +mtllib box-mtllib-red.mtl +mtllib box-mtllib-green.mtl box-mtllib-blue.mtl +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn -1.0000 0.0000 0.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 0.0000 0.0000 1.0000 +usemtl Red +f 3/1/1 7/2/1 5/3/1 1/4/1 +usemtl Green +f 1/9/3 2/10/3 4/11/3 3/12/3 +usemtl Blue +f 3/1/5 4/6/5 8/17/5 7/18/5 +usemtl Red +f 8/5/2 4/6/2 2/7/2 6/8/2 +usemtl Green +f 7/13/4 8/14/4 6/15/4 5/16/4 +usemtl Blue +f 5/19/6 6/20/6 2/7/6 1/4/6 diff --git a/specs/data/box-multiple-materials/box-multiple-materials.mtl b/specs/data/box-multiple-materials/box-multiple-materials.mtl new file mode 100644 index 0000000..7fb6cdb --- /dev/null +++ b/specs/data/box-multiple-materials/box-multiple-materials.mtl @@ -0,0 +1,32 @@ +# Blender MTL File: 'box-multiple-materials.blend' +# Material Count: 3 + +newmtl Blue +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.000000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Green +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.640000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Red +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.000000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-multiple-materials/box-multiple-materials.obj b/specs/data/box-multiple-materials/box-multiple-materials.obj new file mode 100644 index 0000000..5c4848d --- /dev/null +++ b/specs/data/box-multiple-materials/box-multiple-materials.obj @@ -0,0 +1,49 @@ +# Blender v2.78 (sub 0) OBJ File: 'box-multiple-materials.blend' +# www.blender.org +mtllib box-multiple-materials.mtl +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn -1.0000 0.0000 0.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 0.0000 0.0000 1.0000 +usemtl Red +f 3/1/1 7/2/1 5/3/1 1/4/1 +usemtl Green +f 1/9/3 2/10/3 4/11/3 3/12/3 +usemtl Blue +f 3/1/5 4/6/5 8/17/5 7/18/5 +usemtl Red +f 8/5/2 4/6/2 2/7/2 6/8/2 +usemtl Green +f 7/13/4 8/14/4 6/15/4 5/16/4 +usemtl Blue +f 5/19/6 6/20/6 2/7/6 1/4/6 diff --git a/specs/data/box-negative-indices/box-negative-indices.mtl b/specs/data/box-negative-indices/box-negative-indices.mtl new file mode 100644 index 0000000..abbc294 --- /dev/null +++ b/specs/data/box-negative-indices/box-negative-indices.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'box.blend' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-negative-indices/box-negative-indices.obj b/specs/data/box-negative-indices/box-negative-indices.obj new file mode 100644 index 0000000..e3b2aa6 --- /dev/null +++ b/specs/data/box-negative-indices/box-negative-indices.obj @@ -0,0 +1,20 @@ +# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# www.blender.org +mtllib box-negative-indices.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +usemtl Material +s off +f -8 -7 -5 -6 +f -6 -5 -1 -2 +f -2 -1 -3 -4 +f -4 -3 -7 -8 +f -6 -2 -4 -8 +f -1 -5 -7 -3 diff --git a/specs/data/box-no-materials/box-no-materials.obj b/specs/data/box-no-materials/box-no-materials.obj new file mode 100644 index 0000000..a1f2147 --- /dev/null +++ b/specs/data/box-no-materials/box-no-materials.obj @@ -0,0 +1,125 @@ +# Blender v2.78 (sub 0) OBJ File: 'box-objects.blend' +# www.blender.org +v -1.000000 -1.000000 -4.000000 +v -1.000000 1.000000 -4.000000 +v -1.000000 -1.000000 -6.000000 +v -1.000000 1.000000 -6.000000 +v 1.000000 -1.000000 -4.000000 +v 1.000000 1.000000 -4.000000 +v 1.000000 -1.000000 -6.000000 +v 1.000000 1.000000 -6.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 +v 4.000000 -1.000000 1.000000 +v 4.000000 1.000000 1.000000 +v 4.000000 -1.000000 -1.000000 +v 4.000000 1.000000 -1.000000 +v 6.000000 -1.000000 1.000000 +v 6.000000 1.000000 1.000000 +v 6.000000 -1.000000 -1.000000 +v 6.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +s off +f 9/21/7 10/22/7 12/23/7 11/24/7 +f 11/25/8 12/26/8 16/27/8 15/28/8 +f 15/29/9 16/30/9 14/31/9 13/32/9 +f 13/33/10 14/34/10 10/35/10 9/36/10 +f 11/25/11 15/37/11 13/38/11 9/36/11 +f 16/39/12 12/26/12 10/35/12 14/40/12 +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +s off +f 17/41/13 18/42/13 20/43/13 19/44/13 +f 19/45/14 20/46/14 24/47/14 23/48/14 +f 23/49/15 24/50/15 22/51/15 21/52/15 +f 21/53/16 22/54/16 18/55/16 17/56/16 +f 19/45/17 23/57/17 21/58/17 17/56/17 +f 24/59/18 20/46/18 18/55/18 22/60/18 diff --git a/specs/data/box-normals/box-normals.mtl b/specs/data/box-normals/box-normals.mtl new file mode 100644 index 0000000..abbc294 --- /dev/null +++ b/specs/data/box-normals/box-normals.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'box.blend' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-normals/box-normals.obj b/specs/data/box-normals/box-normals.obj new file mode 100644 index 0000000..5d727cc --- /dev/null +++ b/specs/data/box-normals/box-normals.obj @@ -0,0 +1,26 @@ +# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# www.blender.org +mtllib box-normals.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Material +s off +f 1//1 2//1 4//1 3//1 +f 3//2 4//2 8//2 7//2 +f 7//3 8//3 6//3 5//3 +f 5//4 6//4 2//4 1//4 +f 3//5 7//5 5//5 1//5 +f 8//6 4//6 2//6 6//6 diff --git a/specs/data/box-objects-groups-materials/box-objects-groups-materials.gltf b/specs/data/box-objects-groups-materials/box-objects-groups-materials.gltf new file mode 100644 index 0000000..0220c2a --- /dev/null +++ b/specs/data/box-objects-groups-materials/box-objects-groups-materials.gltf @@ -0,0 +1,486 @@ +{ + "accessors": { + "accessor_0": { + "bufferView": "bufferView_vertex", + "byteOffset": 0, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + -1, + -1, + -6 + ], + "max": [ + 1, + 1, + -4 + ], + "type": "VEC3" + }, + "accessor_1": { + "bufferView": "bufferView_vertex", + "byteOffset": 288, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + -1, + -1, + -1 + ], + "max": [ + 1, + 1, + 1 + ], + "type": "VEC3" + }, + "accessor_2": { + "bufferView": "bufferView_vertex", + "byteOffset": 576, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + 0, + 0 + ], + "max": [ + 1, + 1 + ], + "type": "VEC2" + }, + "accessor_3": { + "bufferView": "bufferView_index", + "byteOffset": 0, + "byteStride": 0, + "componentType": 5123, + "count": 18, + "min": [ + 0 + ], + "max": [ + 11 + ], + "type": "SCALAR" + }, + "accessor_4": { + "bufferView": "bufferView_index", + "byteOffset": 36, + "byteStride": 0, + "componentType": 5123, + "count": 18, + "min": [ + 12 + ], + "max": [ + 23 + ], + "type": "SCALAR" + }, + "accessor_5": { + "bufferView": "bufferView_vertex", + "byteOffset": 768, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + 4, + -1, + -1 + ], + "max": [ + 6, + 1, + 1 + ], + "type": "VEC3" + }, + "accessor_6": { + "bufferView": "bufferView_vertex", + "byteOffset": 1056, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + -1, + -1, + -1 + ], + "max": [ + 1, + 1, + 1 + ], + "type": "VEC3" + }, + "accessor_7": { + "bufferView": "bufferView_vertex", + "byteOffset": 1344, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + 0, + 0 + ], + "max": [ + 1, + 1 + ], + "type": "VEC2" + }, + "accessor_8": { + "bufferView": "bufferView_index", + "byteOffset": 72, + "byteStride": 0, + "componentType": 5123, + "count": 18, + "min": [ + 0 + ], + "max": [ + 11 + ], + "type": "SCALAR" + }, + "accessor_9": { + "bufferView": "bufferView_index", + "byteOffset": 108, + "byteStride": 0, + "componentType": 5123, + "count": 18, + "min": [ + 12 + ], + "max": [ + 23 + ], + "type": "SCALAR" + }, + "accessor_10": { + "bufferView": "bufferView_vertex", + "byteOffset": 1536, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + -1, + -1, + -1 + ], + "max": [ + 1, + 1, + 1 + ], + "type": "VEC3" + }, + "accessor_11": { + "bufferView": "bufferView_vertex", + "byteOffset": 1824, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + -1, + -1, + -1 + ], + "max": [ + 1, + 1, + 1 + ], + "type": "VEC3" + }, + "accessor_12": { + "bufferView": "bufferView_vertex", + "byteOffset": 2112, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + 0, + 0 + ], + "max": [ + 1, + 1 + ], + "type": "VEC2" + }, + "accessor_13": { + "bufferView": "bufferView_index", + "byteOffset": 144, + "byteStride": 0, + "componentType": 5123, + "count": 18, + "min": [ + 0 + ], + "max": [ + 11 + ], + "type": "SCALAR" + }, + "accessor_14": { + "bufferView": "bufferView_index", + "byteOffset": 180, + "byteStride": 0, + "componentType": 5123, + "count": 18, + "min": [ + 12 + ], + "max": [ + 23 + ], + "type": "SCALAR" + } + }, + "asset": { + "generator": "obj2gltf", + "profile": { + "api": "WebGL", + "version": "1.0" + }, + "version": "1.0" + }, + "buffers": { + "buffer": { + "byteLength": 2520, + "uri": "data:application/octet-stream;base64,AACAvwAAgL8AAIDAAACAvwAAgD8AAIDAAACAvwAAgD8AAMDAAACAvwAAgL8AAMDAAACAvwAAgL8AAMDAAACAvwAAgD8AAMDAAACAPwAAgD8AAMDAAACAPwAAgL8AAMDAAACAPwAAgL8AAMDAAACAPwAAgD8AAMDAAACAPwAAgD8AAIDAAACAPwAAgL8AAIDAAACAPwAAgL8AAIDAAACAPwAAgD8AAIDAAACAvwAAgD8AAIDAAACAvwAAgL8AAIDAAACAvwAAgL8AAMDAAACAPwAAgL8AAMDAAACAPwAAgL8AAIDAAACAvwAAgL8AAIDAAACAPwAAgD8AAMDAAACAvwAAgD8AAMDAAACAvwAAgD8AAIDAAACAPwAAgD8AAIDAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAACAQAAAgL8AAIA/AACAQAAAgD8AAIA/AACAQAAAgD8AAIC/AACAQAAAgL8AAIC/AACAQAAAgL8AAIC/AACAQAAAgD8AAIC/AADAQAAAgD8AAIC/AADAQAAAgL8AAIC/AADAQAAAgL8AAIC/AADAQAAAgD8AAIC/AADAQAAAgD8AAIA/AADAQAAAgL8AAIA/AADAQAAAgL8AAIA/AADAQAAAgD8AAIA/AACAQAAAgD8AAIA/AACAQAAAgL8AAIA/AACAQAAAgL8AAIC/AADAQAAAgL8AAIC/AADAQAAAgL8AAIA/AACAQAAAgL8AAIA/AADAQAAAgD8AAIC/AACAQAAAgD8AAIC/AACAQAAAgD8AAIA/AADAQAAAgD8AAIA/AACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAACAvwAAgL8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIA/AACAvwAAgL8AAIA/AACAPwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIA/AACAPwAAgD8AAIA/AACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA" + } + }, + "bufferViews": { + "bufferView_vertex": { + "buffer": "buffer", + "byteLength": 2304, + "byteOffset": 0, + "target": 34962 + }, + "bufferView_index": { + "buffer": "buffer", + "byteLength": 216, + "byteOffset": 2304, + "target": 34963 + } + }, + "extensionsUsed": [ + "KHR_materials_common" + ], + "images": {}, + "materials": { + "Blue": { + "extensions": { + "KHR_materials_common": { + "technique": "PHONG", + "values": { + "ambient": [ + 0, + 0, + 0, + 1 + ], + "diffuse": [ + 0, + 0, + 0.64, + 1 + ], + "emission": [ + 0, + 0, + 0, + 1 + ], + "specular": [ + 0.5, + 0.5, + 0.5, + 1 + ], + "shininess": 96.078431, + "transparency": 1, + "transparent": false, + "doubleSided": false + } + } + } + }, + "Green": { + "extensions": { + "KHR_materials_common": { + "technique": "PHONG", + "values": { + "ambient": [ + 0, + 0, + 0, + 1 + ], + "diffuse": [ + 0, + 0.64, + 0, + 1 + ], + "emission": [ + 0, + 0, + 0, + 1 + ], + "specular": [ + 0.5, + 0.5, + 0.5, + 1 + ], + "shininess": 96.078431, + "transparency": 1, + "transparent": false, + "doubleSided": false + } + } + } + }, + "Red": { + "extensions": { + "KHR_materials_common": { + "technique": "PHONG", + "values": { + "ambient": [ + 0, + 0, + 0, + 1 + ], + "diffuse": [ + 0.64, + 0, + 0, + 1 + ], + "emission": [ + 0, + 0, + 0, + 1 + ], + "specular": [ + 0.5, + 0.5, + 0.5, + 1 + ], + "shininess": 96.078431, + "transparency": 1, + "transparent": false, + "doubleSided": false + } + } + } + } + }, + "meshes": { + "CubeBlue_CubeBlue_Blue": { + "name": "CubeBlue_CubeBlue_Blue", + "primitives": [ + { + "attributes": { + "POSITION": "accessor_0", + "NORMAL": "accessor_1", + "TEXCOORD_0": "accessor_2" + }, + "indices": "accessor_3", + "material": "Blue", + "mode": 4 + }, + { + "attributes": { + "POSITION": "accessor_0", + "NORMAL": "accessor_1", + "TEXCOORD_0": "accessor_2" + }, + "indices": "accessor_4", + "material": "Green", + "mode": 4 + } + ] + }, + "CubeGreen_CubeGreen_Green": { + "name": "CubeGreen_CubeGreen_Green", + "primitives": [ + { + "attributes": { + "POSITION": "accessor_5", + "NORMAL": "accessor_6", + "TEXCOORD_0": "accessor_7" + }, + "indices": "accessor_8", + "material": "Green", + "mode": 4 + }, + { + "attributes": { + "POSITION": "accessor_5", + "NORMAL": "accessor_6", + "TEXCOORD_0": "accessor_7" + }, + "indices": "accessor_9", + "material": "Red", + "mode": 4 + } + ] + }, + "CubeRed_CubeRed_Red": { + "name": "CubeRed_CubeRed_Red", + "primitives": [ + { + "attributes": { + "POSITION": "accessor_10", + "NORMAL": "accessor_11", + "TEXCOORD_0": "accessor_12" + }, + "indices": "accessor_13", + "material": "Red", + "mode": 4 + }, + { + "attributes": { + "POSITION": "accessor_10", + "NORMAL": "accessor_11", + "TEXCOORD_0": "accessor_12" + }, + "indices": "accessor_14", + "material": "Blue", + "mode": 4 + } + ] + } + }, + "nodes": { + "Cube": { + "name": "Cube", + "meshes": [ + "CubeBlue_CubeBlue_Blue", + "CubeGreen_CubeGreen_Green", + "CubeRed_CubeRed_Red" + ] + } + }, + "samplers": {}, + "scene": "scene", + "scenes": { + "scene": { + "nodes": [ + "Cube" + ] + } + }, + "textures": {} +} diff --git a/specs/data/box-objects-groups-materials/box-objects-groups-materials.mtl b/specs/data/box-objects-groups-materials/box-objects-groups-materials.mtl new file mode 100644 index 0000000..2f9b11a --- /dev/null +++ b/specs/data/box-objects-groups-materials/box-objects-groups-materials.mtl @@ -0,0 +1,32 @@ +# Blender MTL File: 'box-objects.blend' +# Material Count: 3 + +newmtl Blue +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.000000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Green +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.640000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Red +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.000000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-objects-groups-materials/box-objects-groups-materials.obj b/specs/data/box-objects-groups-materials/box-objects-groups-materials.obj new file mode 100644 index 0000000..1bb7698 --- /dev/null +++ b/specs/data/box-objects-groups-materials/box-objects-groups-materials.obj @@ -0,0 +1,133 @@ +# Blender v2.78 (sub 0) OBJ File: 'box-objects.blend' +# www.blender.org +mtllib box-objects-groups-materials.mtl +o Cube +v -1.000000 -1.000000 -4.000000 +v -1.000000 1.000000 -4.000000 +v -1.000000 -1.000000 -6.000000 +v -1.000000 1.000000 -6.000000 +v 1.000000 -1.000000 -4.000000 +v 1.000000 1.000000 -4.000000 +v 1.000000 -1.000000 -6.000000 +v 1.000000 1.000000 -6.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +g CubeBlue_CubeBlue_Blue +usemtl Blue +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +usemtl Green +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 +v 4.000000 -1.000000 1.000000 +v 4.000000 1.000000 1.000000 +v 4.000000 -1.000000 -1.000000 +v 4.000000 1.000000 -1.000000 +v 6.000000 -1.000000 1.000000 +v 6.000000 1.000000 1.000000 +v 6.000000 -1.000000 -1.000000 +v 6.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +g CubeGreen_CubeGreen_Green +usemtl Green +f 9/21/7 10/22/7 12/23/7 11/24/7 +f 11/25/8 12/26/8 16/27/8 15/28/8 +f 15/29/9 16/30/9 14/31/9 13/32/9 +usemtl Red +f 13/33/10 14/34/10 10/35/10 9/36/10 +f 11/25/11 15/37/11 13/38/11 9/36/11 +f 16/39/12 12/26/12 10/35/12 14/40/12 +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +g CubeRed_CubeRed_Red +usemtl Red +f 17/41/13 18/42/13 20/43/13 19/44/13 +f 19/45/14 20/46/14 24/47/14 23/48/14 +f 23/49/15 24/50/15 22/51/15 21/52/15 +usemtl Blue +f 21/53/16 22/54/16 18/55/16 17/56/16 +f 19/45/17 23/57/17 21/58/17 17/56/17 +f 24/59/18 20/46/18 18/55/18 22/60/18 diff --git a/specs/data/box-objects-groups/box-objects-groups.mtl b/specs/data/box-objects-groups/box-objects-groups.mtl new file mode 100644 index 0000000..2f9b11a --- /dev/null +++ b/specs/data/box-objects-groups/box-objects-groups.mtl @@ -0,0 +1,32 @@ +# Blender MTL File: 'box-objects.blend' +# Material Count: 3 + +newmtl Blue +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.000000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Green +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.640000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Red +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.000000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-objects-groups/box-objects-groups.obj b/specs/data/box-objects-groups/box-objects-groups.obj new file mode 100644 index 0000000..2672d19 --- /dev/null +++ b/specs/data/box-objects-groups/box-objects-groups.obj @@ -0,0 +1,135 @@ +# Blender v2.78 (sub 0) OBJ File: 'box-objects.blend' +# www.blender.org +mtllib box-objects-groups.mtl +o CubeBlue +v -1.000000 -1.000000 -4.000000 +v -1.000000 1.000000 -4.000000 +v -1.000000 -1.000000 -6.000000 +v -1.000000 1.000000 -6.000000 +v 1.000000 -1.000000 -4.000000 +v 1.000000 1.000000 -4.000000 +v 1.000000 -1.000000 -6.000000 +v 1.000000 1.000000 -6.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +g CubeBlue_CubeBlue_Blue +usemtl Blue +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 +o CubeGreen +v 4.000000 -1.000000 1.000000 +v 4.000000 1.000000 1.000000 +v 4.000000 -1.000000 -1.000000 +v 4.000000 1.000000 -1.000000 +v 6.000000 -1.000000 1.000000 +v 6.000000 1.000000 1.000000 +v 6.000000 -1.000000 -1.000000 +v 6.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +g CubeGreen_CubeGreen_Green +usemtl Green +s off +f 9/21/7 10/22/7 12/23/7 11/24/7 +f 11/25/8 12/26/8 16/27/8 15/28/8 +f 15/29/9 16/30/9 14/31/9 13/32/9 +f 13/33/10 14/34/10 10/35/10 9/36/10 +f 11/25/11 15/37/11 13/38/11 9/36/11 +f 16/39/12 12/26/12 10/35/12 14/40/12 +o CubeRed +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +g CubeRed_CubeRed_Red +usemtl Red +s off +f 17/41/13 18/42/13 20/43/13 19/44/13 +f 19/45/14 20/46/14 24/47/14 23/48/14 +f 23/49/15 24/50/15 22/51/15 21/52/15 +f 21/53/16 22/54/16 18/55/16 17/56/16 +f 19/45/17 23/57/17 21/58/17 17/56/17 +f 24/59/18 20/46/18 18/55/18 22/60/18 diff --git a/specs/data/box-objects/box-objects.mtl b/specs/data/box-objects/box-objects.mtl new file mode 100644 index 0000000..2f9b11a --- /dev/null +++ b/specs/data/box-objects/box-objects.mtl @@ -0,0 +1,32 @@ +# Blender MTL File: 'box-objects.blend' +# Material Count: 3 + +newmtl Blue +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.000000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Green +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.640000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Red +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.000000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-objects/box-objects.obj b/specs/data/box-objects/box-objects.obj new file mode 100644 index 0000000..0f86b05 --- /dev/null +++ b/specs/data/box-objects/box-objects.obj @@ -0,0 +1,132 @@ +# Blender v2.78 (sub 0) OBJ File: 'box-objects.blend' +# www.blender.org +mtllib box-objects.mtl +o CubeBlue +v -1.000000 -1.000000 -4.000000 +v -1.000000 1.000000 -4.000000 +v -1.000000 -1.000000 -6.000000 +v -1.000000 1.000000 -6.000000 +v 1.000000 -1.000000 -4.000000 +v 1.000000 1.000000 -4.000000 +v 1.000000 -1.000000 -6.000000 +v 1.000000 1.000000 -6.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Blue +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 +o CubeGreen +v 4.000000 -1.000000 1.000000 +v 4.000000 1.000000 1.000000 +v 4.000000 -1.000000 -1.000000 +v 4.000000 1.000000 -1.000000 +v 6.000000 -1.000000 1.000000 +v 6.000000 1.000000 1.000000 +v 6.000000 -1.000000 -1.000000 +v 6.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Green +s off +f 9/21/7 10/22/7 12/23/7 11/24/7 +f 11/25/8 12/26/8 16/27/8 15/28/8 +f 15/29/9 16/30/9 14/31/9 13/32/9 +f 13/33/10 14/34/10 10/35/10 9/36/10 +f 11/25/11 15/37/11 13/38/11 9/36/11 +f 16/39/12 12/26/12 10/35/12 14/40/12 +o CubeRed +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Red +s off +f 17/41/13 18/42/13 20/43/13 19/44/13 +f 19/45/14 20/46/14 24/47/14 23/48/14 +f 23/49/15 24/50/15 22/51/15 21/52/15 +f 21/53/16 22/54/16 18/55/16 17/56/16 +f 19/45/17 23/57/17 21/58/17 17/56/17 +f 24/59/18 20/46/18 18/55/18 22/60/18 diff --git a/specs/data/box-positions-only/box-positions-only.mtl b/specs/data/box-positions-only/box-positions-only.mtl new file mode 100644 index 0000000..abbc294 --- /dev/null +++ b/specs/data/box-positions-only/box-positions-only.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'box.blend' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-positions-only/box-positions-only.obj b/specs/data/box-positions-only/box-positions-only.obj new file mode 100644 index 0000000..8d2353f --- /dev/null +++ b/specs/data/box-positions-only/box-positions-only.obj @@ -0,0 +1,20 @@ +# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# www.blender.org +mtllib box-positions-only.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +usemtl Material +s off +f 1 2 4 3 +f 3 4 8 7 +f 7 8 6 5 +f 5 6 2 1 +f 3 7 5 1 +f 8 4 2 6 diff --git a/specs/data/box-subdirectories/box-textured.obj b/specs/data/box-subdirectories/box-textured.obj new file mode 100644 index 0000000..faf48db --- /dev/null +++ b/specs/data/box-subdirectories/box-textured.obj @@ -0,0 +1,46 @@ +# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# www.blender.org +mtllib materials/box-textured.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Material +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/data/box-subdirectories/materials/box-textured.mtl b/specs/data/box-subdirectories/materials/box-textured.mtl new file mode 100644 index 0000000..d042c1d --- /dev/null +++ b/specs/data/box-subdirectories/materials/box-textured.mtl @@ -0,0 +1,13 @@ +# Blender MTL File: 'box.blend' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 +map_Kd images/cesium.png diff --git a/specs/data/box-subdirectories/materials/images/cesium.png b/specs/data/box-subdirectories/materials/images/cesium.png new file mode 100644 index 0000000000000000000000000000000000000000..3b8baee1bce0079bd7afeb38d5e0b1bde9732ba5 GIT binary patch literal 7665 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 literal 0 HcmV?d00001 diff --git a/specs/data/box-textured/box-textured.mtl b/specs/data/box-textured/box-textured.mtl new file mode 100644 index 0000000..c5c879f --- /dev/null +++ b/specs/data/box-textured/box-textured.mtl @@ -0,0 +1,13 @@ +# Blender MTL File: 'box.blend' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 +map_Kd cesium.png diff --git a/specs/data/box-textured/box-textured.obj b/specs/data/box-textured/box-textured.obj new file mode 100644 index 0000000..4f5dc44 --- /dev/null +++ b/specs/data/box-textured/box-textured.obj @@ -0,0 +1,46 @@ +# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# www.blender.org +mtllib box-textured.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Material +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/data/box-textured/cesium.png b/specs/data/box-textured/cesium.png new file mode 100644 index 0000000000000000000000000000000000000000..3b8baee1bce0079bd7afeb38d5e0b1bde9732ba5 GIT binary patch literal 7665 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 literal 0 HcmV?d00001 diff --git a/specs/data/box-triangles/box-triangles.mtl b/specs/data/box-triangles/box-triangles.mtl new file mode 100644 index 0000000..abbc294 --- /dev/null +++ b/specs/data/box-triangles/box-triangles.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'box.blend' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-triangles/box-triangles.obj b/specs/data/box-triangles/box-triangles.obj new file mode 100644 index 0000000..124ab2d --- /dev/null +++ b/specs/data/box-triangles/box-triangles.obj @@ -0,0 +1,46 @@ +# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# www.blender.org +mtllib box-triangles.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Material +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/data/box-uncleaned/box-uncleaned.mtl b/specs/data/box-uncleaned/box-uncleaned.mtl new file mode 100644 index 0000000..a304b43 --- /dev/null +++ b/specs/data/box-uncleaned/box-uncleaned.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'None' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-uncleaned/box-uncleaned.obj b/specs/data/box-uncleaned/box-uncleaned.obj new file mode 100644 index 0000000..7558d99 --- /dev/null +++ b/specs/data/box-uncleaned/box-uncleaned.obj @@ -0,0 +1,52 @@ +# Blender v2.78 (sub 0) OBJ File: '' +# www.blender.org +mtllib box-uncleaned.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +g Cube +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +o Cube +g Cube +usemtl Material +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 +g Cube +usemtl Material +o Cube diff --git a/specs/data/box-usemtl/box-usemtl.mtl b/specs/data/box-usemtl/box-usemtl.mtl new file mode 100644 index 0000000..2f9b11a --- /dev/null +++ b/specs/data/box-usemtl/box-usemtl.mtl @@ -0,0 +1,32 @@ +# Blender MTL File: 'box-objects.blend' +# Material Count: 3 + +newmtl Blue +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.000000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Green +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.640000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Red +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.000000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-usemtl/box-usemtl.obj b/specs/data/box-usemtl/box-usemtl.obj new file mode 100644 index 0000000..2350468 --- /dev/null +++ b/specs/data/box-usemtl/box-usemtl.obj @@ -0,0 +1,129 @@ +# Blender v2.78 (sub 0) OBJ File: 'box-objects.blend' +# www.blender.org +mtllib box-usemtl.mtl +v -1.000000 -1.000000 -4.000000 +v -1.000000 1.000000 -4.000000 +v -1.000000 -1.000000 -6.000000 +v -1.000000 1.000000 -6.000000 +v 1.000000 -1.000000 -4.000000 +v 1.000000 1.000000 -4.000000 +v 1.000000 -1.000000 -6.000000 +v 1.000000 1.000000 -6.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Blue +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 +v 4.000000 -1.000000 1.000000 +v 4.000000 1.000000 1.000000 +v 4.000000 -1.000000 -1.000000 +v 4.000000 1.000000 -1.000000 +v 6.000000 -1.000000 1.000000 +v 6.000000 1.000000 1.000000 +v 6.000000 -1.000000 -1.000000 +v 6.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Green +s off +f 9/21/7 10/22/7 12/23/7 11/24/7 +f 11/25/8 12/26/8 16/27/8 15/28/8 +f 15/29/9 16/30/9 14/31/9 13/32/9 +f 13/33/10 14/34/10 10/35/10 9/36/10 +f 11/25/11 15/37/11 13/38/11 9/36/11 +f 16/39/12 12/26/12 10/35/12 14/40/12 +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Red +s off +f 17/41/13 18/42/13 20/43/13 19/44/13 +f 19/45/14 20/46/14 24/47/14 23/48/14 +f 23/49/15 24/50/15 22/51/15 21/52/15 +f 21/53/16 22/54/16 18/55/16 17/56/16 +f 19/45/17 23/57/17 21/58/17 17/56/17 +f 24/59/18 20/46/18 18/55/18 22/60/18 diff --git a/specs/data/box-uvs/box-uvs.mtl b/specs/data/box-uvs/box-uvs.mtl new file mode 100644 index 0000000..abbc294 --- /dev/null +++ b/specs/data/box-uvs/box-uvs.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'box.blend' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-uvs/box-uvs.obj b/specs/data/box-uvs/box-uvs.obj new file mode 100644 index 0000000..9beef82 --- /dev/null +++ b/specs/data/box-uvs/box-uvs.obj @@ -0,0 +1,40 @@ +# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# www.blender.org +mtllib box-uvs.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +usemtl Material +s off +f 1/1 2/2 4/3 3/4 +f 3/5 4/6 8/7 7/8 +f 7/9 8/10 6/11 5/12 +f 5/13 6/14 2/15 1/16 +f 3/5 7/17 5/18 1/16 +f 8/19 4/6 2/15 6/20 diff --git a/specs/data/box/box.gltf b/specs/data/box/box.gltf new file mode 100644 index 0000000..0f8ecdb --- /dev/null +++ b/specs/data/box/box.gltf @@ -0,0 +1,176 @@ +{ + "accessors": { + "accessor_0": { + "bufferView": "bufferView_vertex", + "byteOffset": 0, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + -1, + -1, + -1 + ], + "max": [ + 1, + 1, + 1 + ], + "type": "VEC3" + }, + "accessor_1": { + "bufferView": "bufferView_vertex", + "byteOffset": 288, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + -1, + -1, + -1 + ], + "max": [ + 1, + 1, + 1 + ], + "type": "VEC3" + }, + "accessor_2": { + "bufferView": "bufferView_vertex", + "byteOffset": 576, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + 0, + 0 + ], + "max": [ + 1, + 1 + ], + "type": "VEC2" + }, + "accessor_3": { + "bufferView": "bufferView_index", + "byteOffset": 0, + "byteStride": 0, + "componentType": 5123, + "count": 36, + "min": [ + 0 + ], + "max": [ + 23 + ], + "type": "SCALAR" + } + }, + "asset": { + "generator": "obj2gltf", + "profile": { + "api": "WebGL", + "version": "1.0" + }, + "version": "1.0" + }, + "buffers": { + "buffer": { + "byteLength": 840, + "uri": "data:application/octet-stream;base64,AACAvwAAgL8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIA/AACAvwAAgL8AAIA/AACAPwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIA/AACAPwAAgD8AAIA/AACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA" + } + }, + "bufferViews": { + "bufferView_vertex": { + "buffer": "buffer", + "byteLength": 768, + "byteOffset": 0, + "target": 34962 + }, + "bufferView_index": { + "buffer": "buffer", + "byteLength": 72, + "byteOffset": 768, + "target": 34963 + } + }, + "extensionsUsed": [ + "KHR_materials_common" + ], + "images": {}, + "materials": { + "Material": { + "extensions": { + "KHR_materials_common": { + "technique": "PHONG", + "values": { + "ambient": [ + 0, + 0, + 0, + 1 + ], + "diffuse": [ + 0.64, + 0.64, + 0.64, + 1 + ], + "emission": [ + 0, + 0, + 0, + 1 + ], + "specular": [ + 0.5, + 0.5, + 0.5, + 1 + ], + "shininess": 96.078431, + "transparency": 1, + "transparent": false, + "doubleSided": false + } + } + } + } + }, + "meshes": { + "Cube-Mesh": { + "name": "Cube-Mesh", + "primitives": [ + { + "attributes": { + "POSITION": "accessor_0", + "NORMAL": "accessor_1", + "TEXCOORD_0": "accessor_2" + }, + "indices": "accessor_3", + "material": "Material", + "mode": 4 + } + ] + } + }, + "nodes": { + "Cube": { + "name": "Cube", + "meshes": [ + "Cube-Mesh" + ] + } + }, + "samplers": {}, + "scene": "scene", + "scenes": { + "scene": { + "nodes": [ + "Cube" + ] + } + }, + "textures": {} +} \ No newline at end of file diff --git a/specs/data/box/box.mtl b/specs/data/box/box.mtl new file mode 100644 index 0000000..a304b43 --- /dev/null +++ b/specs/data/box/box.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'None' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box/box.obj b/specs/data/box/box.obj new file mode 100644 index 0000000..5246261 --- /dev/null +++ b/specs/data/box/box.obj @@ -0,0 +1,46 @@ +# Blender v2.78 (sub 0) OBJ File: '' +# www.blender.org +mtllib box.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Material +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/lib/convertSpec.js b/specs/lib/convertSpec.js index a001844..401cb83 100644 --- a/specs/lib/convertSpec.js +++ b/specs/lib/convertSpec.js @@ -1,23 +1,123 @@ 'use strict'; -var Promise = require('bluebird'); - +var fsExtra = require('fs-extra'); var GltfPipeline = require('gltf-pipeline').Pipeline; var path = require('path'); var convert = require('../../lib/convert'); -var objFile = './specs/data/BoxTextured/BoxTextured.obj'; -var gltfFile = './specs/data/BoxTextured/BoxTextured.gltf'; +var objPath = 'specs/data/box-textured/box-textured.obj'; +var gltfPath = 'specs/data/box-textured/box-textured.gltf'; +var glbPath = 'specs/data/box-textured/box-textured.glb'; describe('convert', function() { it('converts an obj to gltf', function(done) { - var spy = spyOn(GltfPipeline, 'processJSONToDisk').and.callFake(function(gltf, outputPath, options, callback) { - return; - }); - expect(convert(objFile, gltfFile, {}) + var spy = spyOn(GltfPipeline, 'processJSONToDisk'); + expect(convert(objPath, gltfPath) .then(function() { var args = spy.calls.first().args; - expect(args[0]).toBeDefined(); - expect(path.normalize(args[1])).toEqual(path.normalize(gltfFile)); + var gltf = args[0]; + var outputPath = args[1]; + expect(path.normalize(outputPath)).toEqual(path.normalize(gltfPath)); + expect(gltf).toBeDefined(); + expect(gltf.images.cesium).toBeDefined(); }), done).toResolve(); }); + + it('uses default gltf-pipeline options', function(done) { + var spy = spyOn(GltfPipeline, 'processJSONToDisk'); + expect(convert(objPath, gltfPath) + .then(function() { + var args = spy.calls.first().args; + var options = args[2]; + expect(options).toEqual({ + createDirectory : false, + basePath : path.dirname(objPath), + binary : false, + embed : true, + embedImage : true, + encodeNormals : false, + quantize : false, + compressTextureCoordinates : false, + aoOptions : undefined, + smoothNormals : false, + optimizeForCesium : false, + textureCompressionOptions : undefined, + preserve : true + }); + }), done).toResolve(); + }); + + it('sets options', function(done) { + var spy = spyOn(GltfPipeline, 'processJSONToDisk'); + var textureCompressionOptions = { + format : 'dxt1', + quality : 10 + }; + var options = { + binary : true, + separate : true, + separateTextures : true, + compress : true, + optimize : true, + generateNormals : true, + ao : true, + optimizeForCesium : true, + textureCompressionOptions : textureCompressionOptions + }; + + expect(convert(objPath, gltfPath, options) + .then(function() { + var args = spy.calls.first().args; + var options = args[2]; + expect(options).toEqual({ + createDirectory : false, + basePath : path.dirname(objPath), + binary : true, + embed : false, + embedImage : false, + encodeNormals : true, + quantize : true, + compressTextureCoordinates : true, + aoOptions : {}, + smoothNormals : true, + optimizeForCesium : true, + textureCompressionOptions : textureCompressionOptions, + preserve : false + }); + }), done).toResolve(); + }); + + it('saves as binary if gltfPath has a .glb extension', function(done) { + var spy = spyOn(GltfPipeline, 'processJSONToDisk'); + expect(convert(objPath, glbPath) + .then(function() { + var args = spy.calls.first().args; + var options = args[2]; + expect(options.binary).toBe(true); + }), done).toResolve(); + }); + + it('bypassPipeline flag bypasses gltf-pipeline', function(done) { + var spy1 = spyOn(convert, '_outputJson'); + var spy2 = spyOn(GltfPipeline, 'processJSONToDisk'); + var options = { + bypassPipeline : true + }; + expect(convert(objPath, gltfPath, options) + .then(function() { + expect(spy1.calls.count()).toBe(1); + expect(spy2.calls.count()).toBe(0); + }), done).toResolve(); + }); + + it('throws if objPath is undefined', function() { + expect(function() { + convert(undefined, gltfPath); + }).toThrowDeveloperError(); + }); + + it('throws if gltfPath is undefined', function() { + expect(function() { + convert(objPath, undefined); + }).toThrowDeveloperError(); + }); }); diff --git a/specs/lib/gltfSpec.js b/specs/lib/gltfSpec.js new file mode 100644 index 0000000..3ff7714 --- /dev/null +++ b/specs/lib/gltfSpec.js @@ -0,0 +1,355 @@ +'use strict'; +var Cesium = require('cesium'); +var fsExtra = require('fs-extra'); +var path = require('path'); +var Promise = require('bluebird'); +var clone = require('../../lib/clone.js'); +var createGltf = require('../../lib/gltf.js'); +var loadImage = require('../../lib/image.js'); +var loadObj = require('../../lib/obj.js'); + +var WebGLConstants = Cesium.WebGLConstants; + +var fsExtraReadJson = Promise.promisify(fsExtra.readJson); + +var boxObjUrl = 'specs/data/box/box.obj'; +var groupObjUrl = 'specs/data/box-objects-groups-materials/box-objects-groups-materials.obj'; +var boxGltfUrl = 'specs/data/box/box.gltf'; +var groupGltfUrl = 'specs/data/box-objects-groups-materials/box-objects-groups-materials.gltf'; +var diffuseTextureUrl = 'specs/data/box-textured/cesium.png'; +var transparentDiffuseTextureUrl = 'specs/data/box-complex-material/diffuse.png'; + +describe('gltf', function() { + var boxObjData; + var groupObjData; + var boxGltf; + var groupGltf; + var diffuseTexture; + var transparentDiffuseTexture; + + beforeAll(function(done) { + return Promise.all([ + loadObj(boxObjUrl) + .then(function(data) { + boxObjData = data; + }), + loadObj(groupObjUrl) + .then(function(data) { + groupObjData = data; + }), + fsExtraReadJson(boxGltfUrl) + .then(function(gltf) { + boxGltf = gltf; + }), + fsExtraReadJson(groupGltfUrl) + .then(function(gltf) { + groupGltf = gltf; + }), + loadImage(diffuseTextureUrl) + .then(function(image) { + diffuseTexture = image; + }), + loadImage(transparentDiffuseTextureUrl) + .then(function(image) { + transparentDiffuseTexture = image; + }) + ]).then(done); + }); + + it('simple gltf', function() { + var objData = clone(boxObjData, true); + var gltf = createGltf(objData); + expect(gltf).toEqual(boxGltf); + }); + + it('multiple nodes, meshes, and primitives', function() { + var objData = clone(groupObjData, true); + var gltf = createGltf(objData); + expect(gltf).toEqual(groupGltf); + + expect(Object.keys(gltf.materials).length).toBe(3); + expect(Object.keys(gltf.nodes).length).toBe(1); + expect(Object.keys(gltf.meshes).length).toBe(3); + + // Check for two primitives in each mesh + for (var id in gltf.meshes) { + if (gltf.meshes.hasOwnProperty(id)) { + var mesh = gltf.meshes[id]; + expect(mesh.primitives.length).toBe(2); + } + } + }); + + it('sets default material values', function() { + var objData = clone(boxObjData, true); + objData.materials.Material = {}; + + var gltf = createGltf(objData); + var material = gltf.materials.Material; + var kmc = material.extensions.KHR_materials_common; + var values = kmc.values; + + expect(kmc.technique).toBe('LAMBERT'); + expect(values.ambient).toEqual([0.0, 0.0, 0.0, 1]); + expect(values.diffuse).toEqual([0.5, 0.5, 0.5, 1]); + expect(values.emission).toEqual([0.0, 0.0, 0.0, 1]); + expect(values.specular).toEqual([0.0, 0.0, 0.0, 1]); + expect(values.shininess).toEqual(0.0); + }); + + it('sets material for diffuse texture', function() { + var objData = clone(boxObjData, true); + objData.materials.Material = { + diffuseColorMap : diffuseTextureUrl + }; + objData.images[diffuseTextureUrl] = diffuseTexture; + + var gltf = createGltf(objData); + var kmc = gltf.materials.Material.extensions.KHR_materials_common; + var texture = gltf.textures.texture_cesium; + var image = gltf.images.cesium; + + expect(kmc.technique).toBe('LAMBERT'); + expect(kmc.values.diffuse).toEqual('texture_cesium'); + expect(kmc.values.transparency).toBe(1.0); + expect(kmc.values.transparent).toBe(false); + expect(kmc.values.doubleSided).toBe(false); + + expect(texture).toEqual({ + format : WebGLConstants.RGB, + internalFormat : WebGLConstants.RGB, + sampler : 'sampler', + source : 'cesium', + target : WebGLConstants.TEXTURE_2D, + type : WebGLConstants.UNSIGNED_BYTE + }); + + expect(image).toBeDefined(); + expect(image.name).toBe('cesium'); + expect(image.uri.indexOf('data:image/png;base64,') >= 0).toBe(true); + + expect(gltf.samplers.sampler).toEqual({ + magFilter : WebGLConstants.LINEAR, + minFilter : WebGLConstants.LINEAR, + wrapS : WebGLConstants.REPEAT, + wrapT : WebGLConstants.REPEAT + }); + }); + + it('sets material for alpha less than 1', function() { + var objData = clone(boxObjData, true); + objData.materials.Material = { + alpha : 0.4 + }; + + var gltf = createGltf(objData); + var kmc = gltf.materials.Material.extensions.KHR_materials_common; + + expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 0.4]); + expect(kmc.values.transparency).toBe(1.0); + expect(kmc.values.transparent).toBe(true); + expect(kmc.values.doubleSided).toBe(true); + }); + + it('sets material for diffuse texture and alpha less than 1', function() { + var objData = clone(boxObjData, true); + objData.materials.Material = { + diffuseColorMap : diffuseTextureUrl, + alpha : 0.4 + }; + objData.images[diffuseTextureUrl] = diffuseTexture; + + var gltf = createGltf(objData); + var kmc = gltf.materials.Material.extensions.KHR_materials_common; + + expect(kmc.values.diffuse).toEqual('texture_cesium'); + expect(kmc.values.transparency).toBe(0.4); + expect(kmc.values.transparent).toBe(true); + expect(kmc.values.doubleSided).toBe(true); + }); + + it('sets material for transparent diffuse texture', function() { + var objData = clone(boxObjData, true); + objData.materials.Material = { + diffuseColorMap : transparentDiffuseTextureUrl + }; + objData.images[transparentDiffuseTextureUrl] = transparentDiffuseTexture; + + var gltf = createGltf(objData); + var kmc = gltf.materials.Material.extensions.KHR_materials_common; + + expect(kmc.values.diffuse).toBe('texture_diffuse'); + expect(kmc.values.transparency).toBe(1.0); + expect(kmc.values.transparent).toBe(true); + expect(kmc.values.doubleSided).toBe(true); + }); + + it('sets material for specular', function() { + var objData = clone(boxObjData, true); + objData.materials.Material = { + specularColor : [0.1, 0.1, 0.2, 1], + specularShininess : 0.1 + }; + + var gltf = createGltf(objData); + var kmc = gltf.materials.Material.extensions.KHR_materials_common; + + expect(kmc.technique).toBe('PHONG'); + expect(kmc.values.specular).toEqual([0.1, 0.1, 0.2, 1]); + expect(kmc.values.shininess).toEqual(0.1); + }); + + it('sets constant material when there are no normals', function() { + var objData = clone(boxObjData, true); + objData.nodes[0].meshes[0].normals.length = 0; + objData.materials.Material = { + diffuseColorMap : diffuseTextureUrl + }; + objData.images[diffuseTextureUrl] = diffuseTexture; + + var gltf = createGltf(objData); + var kmc = gltf.materials.Material.extensions.KHR_materials_common; + + expect(kmc.technique).toBe('CONSTANT'); + expect(kmc.values.emission).toEqual('texture_cesium'); + }); + + it('sets default material when texture is missing', function() { + var objData = clone(boxObjData, true); + objData.materials.Material = { + diffuseColorMap : diffuseTextureUrl + }; + + var gltf = createGltf(objData); + var kmc = gltf.materials.Material.extensions.KHR_materials_common; + + expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]); + }); + + it('uses default material (1)', function() { + var objData = clone(boxObjData, true); + objData.nodes[0].meshes[0].primitives[0].material = undefined; + + // Creates a material called "default" + var gltf = createGltf(objData); + expect(gltf.materials.default).toBeDefined(); + var kmc = gltf.materials.default.extensions.KHR_materials_common; + expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]); + }); + + it('uses default material (2)', function() { + var objData = clone(boxObjData, true); + objData.materials = {}; + + // Uses the original name of the material + var gltf = createGltf(objData); + var kmc = gltf.materials.Material.extensions.KHR_materials_common; + + expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]); + }); + + it('handles material used with and without normals', function() { + // Two meshes - one with normals, and one without + var objData = clone(boxObjData, true); + objData.nodes.push(clone(objData.nodes[0], true)); + objData.nodes[1].meshes[0].normals.length = 0; + + var gltf = createGltf(objData); + var kmc1 = gltf.materials.Material.extensions.KHR_materials_common; + var kmc2 = gltf.materials.Material_constant.extensions.KHR_materials_common; + + expect(kmc1.technique).toBe('PHONG'); + expect(kmc2.technique).toBe('CONSTANT'); + + // Now test in a different order + objData = clone(boxObjData, true); + objData.nodes.push(clone(objData.nodes[0], true)); + objData.nodes[0].meshes[0].normals.length = 0; + + gltf = createGltf(objData); + kmc1 = gltf.materials.Material.extensions.KHR_materials_common; + kmc2 = gltf.materials.Material_shaded.extensions.KHR_materials_common; + + expect(kmc1.technique).toBe('CONSTANT'); + expect(kmc2.technique).toBe('PHONG'); + }); + + it('runs without normals', function() { + var objData = clone(boxObjData, true); + objData.nodes[0].meshes[0].normals.length = 0; + + var gltf = createGltf(objData); + var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes; + expect(attributes.POSITION).toBeDefined(); + expect(attributes.NORMAL).toBeUndefined(); + expect(attributes.TEXCOORD_0).toBeDefined(); + }); + + it('runs without uvs', function() { + var objData = clone(boxObjData, true); + objData.nodes[0].meshes[0].uvs.length = 0; + + var gltf = createGltf(objData); + var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes; + expect(attributes.POSITION).toBeDefined(); + expect(attributes.NORMAL).toBeDefined(); + expect(attributes.TEXCOORD_0).toBeUndefined(); + }); + + it('runs without uvs and normals', function() { + var objData = clone(boxObjData, true); + objData.nodes[0].meshes[0].normals.length = 0; + objData.nodes[0].meshes[0].uvs.length = 0; + + var gltf = createGltf(objData); + var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes; + expect(attributes.POSITION).toBeDefined(); + expect(attributes.NORMAL).toBeUndefined(); + expect(attributes.TEXCOORD_0).toBeUndefined(); + }); + + function expandObjData(objData, duplicatesLength) { + var mesh = objData.nodes[0].meshes[0]; + var indices = mesh.primitives[0].indices; + var positions = mesh.positions; + var normals = mesh.normals; + var uvs = mesh.uvs; + + var indicesLength = indices.length; + var vertexCount = positions.length / 3; + + for (var i = 1; i < duplicatesLength; ++i) { + for (var j = 0; j < vertexCount; ++j) { + positions.push(0.0); + positions.push(0.0); + positions.push(0.0); + normals.push(0.0); + normals.push(0.0); + normals.push(0.0); + uvs.push(0.0); + uvs.push(0.0); + } + for (var k = 0; k < indicesLength; ++k) { + indices.push(indices.get(k) + vertexCount * i); + } + } + } + + it('detects need to use uint32 indices', function() { + var objData = clone(boxObjData, true); + expandObjData(objData, 2731); // Right above 65536 limit + var mesh = objData.nodes[0].meshes[0]; + var indicesLength = mesh.primitives[0].indices.length; + var vertexCount = mesh.positions.length / 3; + + var gltf = createGltf(objData); + var primitive = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0]; + var indicesAccessor = gltf.accessors[primitive.indices]; + expect(indicesAccessor.count).toBe(indicesLength); + expect(indicesAccessor.max[0]).toBe(vertexCount - 1); + expect(indicesAccessor.componentType).toBe(WebGLConstants.UNSIGNED_INT); + + var positionAccessor = gltf.accessors[primitive.attributes.POSITION]; + expect(positionAccessor.count).toBe(vertexCount); + }); +}); diff --git a/specs/lib/imageSpec.js b/specs/lib/imageSpec.js new file mode 100644 index 0000000..cb5359c --- /dev/null +++ b/specs/lib/imageSpec.js @@ -0,0 +1,91 @@ +'use strict'; +var Cesium = require('cesium'); +var path = require('path'); +var loadImage = require('../../lib/image.js'); + +var WebGLConstants = Cesium.WebGLConstants; + +var pngImage = 'specs/data/box-complex-material/shininess.png'; +var jpgImage = 'specs/data/box-complex-material/emission.jpg'; +var jpegImage = 'specs/data/box-complex-material/specular.jpeg'; +var gifImage = 'specs/data/box-complex-material/ambient.gif'; +var grayscaleImage = 'specs/data/box-complex-material/alpha.png'; +var transparentImage = 'specs/data/box-complex-material/diffuse.png'; +var invalidImage = 'invalid.png'; + +describe('image', function() { + it('loads png image', function(done) { + expect(loadImage(pngImage) + .then(function(info) { + expect(info.transparent).toBe(false); + expect(info.channels).toBe(3); + expect(info.data).toBeDefined(); + expect(info.uri.indexOf('data:image/png') === 0).toBe(true); + expect(info.format).toBe(WebGLConstants.RGB); + }), done).toResolve(); + }); + + it('loads jpg image', function(done) { + expect(loadImage(jpgImage) + .then(function(info) { + expect(info.transparent).toBe(false); + expect(info.channels).toBe(3); + expect(info.data).toBeDefined(); + expect(info.uri.indexOf('data:image/jpeg') === 0).toBe(true); + expect(info.format).toBe(WebGLConstants.RGB); + }), done).toResolve(); + }); + + it('loads jpeg image', function(done) { + expect(loadImage(jpegImage) + .then(function(info) { + expect(info.transparent).toBe(false); + expect(info.channels).toBe(3); + expect(info.data).toBeDefined(); + expect(info.uri.indexOf('data:image/jpeg') === 0).toBe(true); + expect(info.format).toBe(WebGLConstants.RGB); + }), done).toResolve(); + }); + + it('loads gif image', function(done) { + expect(loadImage(gifImage) + .then(function(info) { + expect(info.transparent).toBe(false); + expect(info.channels).toBe(3); + expect(info.data).toBeDefined(); + expect(info.uri.indexOf('data:image/gif') === 0).toBe(true); + expect(info.format).toBe(WebGLConstants.RGB); + }), done).toResolve(); + }); + + it('loads grayscale image', function(done) { + expect(loadImage(grayscaleImage) + .then(function(info) { + expect(info.transparent).toBe(false); + expect(info.channels).toBe(1); + expect(info.data).toBeDefined(); + expect(info.uri.indexOf('data:image/png') === 0).toBe(true); + expect(info.format).toBe(WebGLConstants.ALPHA); + }), done).toResolve(); + }); + + it('loads transparentImage image', function(done) { + expect(loadImage(transparentImage) + .then(function(info) { + expect(info.transparent).toBe(true); + expect(info.channels).toBe(4); + expect(info.data).toBeDefined(); + expect(info.uri.indexOf('data:image/png') === 0).toBe(true); + expect(info.format).toBe(WebGLConstants.RGBA); + }), done).toResolve(); + }); + + it('handles invalid image file', function(done) { + spyOn(console, 'log'); + expect(loadImage(invalidImage) + .then(function(image) { + expect(image).toBeUndefined(); + expect(console.log.calls.argsFor(0)[0].indexOf('Could not read image file') >= 0).toBe(true); + }), done).toResolve(); + }); +}); diff --git a/specs/lib/mtlSpec.js b/specs/lib/mtlSpec.js new file mode 100644 index 0000000..36be197 --- /dev/null +++ b/specs/lib/mtlSpec.js @@ -0,0 +1,53 @@ +'use strict'; +var path = require('path'); +var loadMtl = require('../../lib/mtl.js'); + +var complexMaterialUrl = 'specs/data/box-complex-material/box-complex-material.mtl'; +var multipleMaterialsUrl = 'specs/data/box-multiple-materials/box-multiple-materials.mtl'; +var invalidMaterialUrl = 'invalid.mtl'; + +function getImagePath(objPath, relativePath) { + return path.normalize(path.join(path.dirname(objPath), relativePath)); +} + +describe('mtl', function() { + it('loads complex material', function(done) { + expect(loadMtl(complexMaterialUrl) + .then(function(materials) { + var material = materials.Material; + expect(material).toBeDefined(); + expect(material.ambientColor).toEqual([0.2, 0.2, 0.2, 1.0]); + expect(material.emissionColor).toEqual([0.1, 0.1, 0.1, 1.0]); + expect(material.diffuseColor).toEqual([0.64, 0.64, 0.64, 1.0]); + expect(material.specularColor).toEqual([0.5, 0.5, 0.5, 1.0]); + expect(material.specularShininess).toEqual(96.078431); + expect(material.alpha).toEqual(0.9); + expect(material.ambientColorMap).toEqual(getImagePath(complexMaterialUrl, 'ambient.gif')); + expect(material.emissionColorMap).toEqual(getImagePath(complexMaterialUrl, 'emission.jpg')); + expect(material.diffuseColorMap).toEqual(getImagePath(complexMaterialUrl, 'diffuse.png')); + expect(material.specularColorMap).toEqual(getImagePath(complexMaterialUrl, 'specular.jpeg')); + expect(material.specularShininessMap).toEqual(getImagePath(complexMaterialUrl, 'shininess.png')); + expect(material.normalMap).toEqual(getImagePath(complexMaterialUrl, 'bump.png')); + expect(material.alphaMap).toEqual(getImagePath(complexMaterialUrl, 'alpha.png')); + }), done).toResolve(); + }); + + it('loads mtl with multiple materials', function(done) { + expect(loadMtl(multipleMaterialsUrl) + .then(function(materials) { + expect(Object.keys(materials).length).toBe(3); + expect(materials.Red.diffuseColor).toEqual([0.64, 0.0, 0.0, 1.0]); + expect(materials.Green.diffuseColor).toEqual([0.0, 0.64, 0.0, 1.0]); + expect(materials.Blue.diffuseColor).toEqual([0.0, 0.0, 0.64, 1.0]); + }), done).toResolve(); + }); + + it('handles invalid mtl file', function(done) { + spyOn(console, 'log'); + expect(loadMtl(invalidMaterialUrl) + .then(function(materials) { + expect(materials).toEqual({}); + expect(console.log.calls.argsFor(0)[0].indexOf('Could not read material file') >= 0).toBe(true); + }), done).toResolve(); + }); +}); diff --git a/specs/lib/objSpec.js b/specs/lib/objSpec.js new file mode 100644 index 0000000..89f0f91 --- /dev/null +++ b/specs/lib/objSpec.js @@ -0,0 +1,328 @@ +'use strict'; +var Cesium = require('cesium'); +var path = require('path'); +var Promise = require('bluebird'); +var loadObj = require('../../lib/obj.js'); + +var RuntimeError = Cesium.RuntimeError; + +var objUrl = 'specs/data/box/box.obj'; +var objNormalsUrl = 'specs/data/box-normals/box-normals.obj'; +var objUvsUrl = 'specs/data/box-uvs/box-uvs.obj'; +var objPositionsOnlyUrl = 'specs/data/box-positions-only/box-positions-only.obj'; +var objNegativeIndicesUrl = 'specs/data/box-negative-indices/box-negative-indices.obj'; +var objTrianglesUrl = 'specs/data/box-triangles/box-triangles.obj'; +var objObjectsUrl = 'specs/data/box-objects/box-objects.obj'; +var objGroupsUrl = 'specs/data/box-groups/box-groups.obj'; +var objObjectsGroupsUrl = 'specs/data/box-objects-groups/box-objects-groups.obj'; +var objUsemtlUrl = 'specs/data/box-usemtl/box-usemtl.obj'; +var objNoMaterialsUrl = 'specs/data/box-no-materials/box-no-materials.obj'; +var objMultipleMaterialsUrl = 'specs/data/box-multiple-materials/box-multiple-materials.obj'; +var objUncleanedUrl = 'specs/data/box-uncleaned/box-uncleaned.obj'; +var objMtllibUrl = 'specs/data/box-mtllib/box-mtllib.obj'; +var objMissingMtllibUrl = 'specs/data/box-missing-mtllib/box-missing-mtllib.obj'; +var objTexturedUrl = 'specs/data/box-textured/box-textured.obj'; +var objMissingTextureUrl = 'specs/data/box-missing-texture/box-missing-texture.obj'; +var objSubdirectoriesUrl = 'specs/data/box-subdirectories/box-textured.obj'; +var objComplexMaterialUrl = 'specs/data/box-complex-material/box-complex-material.obj'; +var objInvalidContentsUrl = 'specs/data/box/box.mtl'; +var objInvalidUrl = 'invalid.obj'; + +function getMeshes(data) { + var meshes = []; + var nodes = data.nodes; + var nodesLength = nodes.length; + for (var i = 0; i < nodesLength; ++i) { + meshes = meshes.concat(nodes[i].meshes); + } + return meshes; +} + +function getPrimitives(data) { + var primitives = []; + var nodes = data.nodes; + var nodesLength = nodes.length; + for (var i = 0; i < nodesLength; ++i) { + var meshes = nodes[i].meshes; + var meshesLength = meshes.length; + for (var j = 0; j < meshesLength; ++j) { + primitives = primitives.concat(meshes[j].primitives); + } + } + return primitives; +} + +function getImagePath(objPath, relativePath) { + return path.normalize(path.join(path.dirname(objPath), relativePath)); +} + +describe('obj', function() { + it('loads obj with positions, normals, and uvs', function(done) { + expect(loadObj(objUrl) + .then(function(data) { + var images = data.images; + var materials = data.materials; + var nodes = data.nodes; + var meshes = getMeshes(data); + var primitives = getPrimitives(data); + + expect(Object.keys(images).length).toBe(0); + expect(materials.Material).toBeDefined(); + expect(nodes.length).toBe(1); + expect(meshes.length).toBe(1); + expect(primitives.length).toBe(1); + + var node = nodes[0]; + var mesh = meshes[0]; + var primitive = primitives[0]; + + expect(node.name).toBe('Cube'); + expect(mesh.name).toBe('Cube-Mesh'); + expect(mesh.positions.length / 3).toBe(24); + expect(mesh.normals.length / 3).toBe(24); + expect(mesh.uvs.length / 2).toBe(24); + expect(primitive.indices.length).toBe(36); + expect(primitive.material).toBe('Material'); + }), done).toResolve(); + }); + + it('loads obj with normals', function(done) { + expect(loadObj(objNormalsUrl) + .then(function(data) { + var mesh = getMeshes(data)[0]; + expect(mesh.positions.length / 3).toBe(24); + expect(mesh.normals.length / 3).toBe(24); + expect(mesh.uvs.length / 2).toBe(0); + }), done).toResolve(); + }); + + it('loads obj with uvs', function(done) { + expect(loadObj(objUvsUrl) + .then(function(data) { + var mesh = getMeshes(data)[0]; + expect(mesh.positions.length / 3).toBe(20); + expect(mesh.normals.length / 3).toBe(0); + expect(mesh.uvs.length / 2).toBe(20); + }), done).toResolve(); + }); + + it('loads obj with negative indices', function(done) { + expect(Promise.all([ + loadObj(objPositionsOnlyUrl), + loadObj(objNegativeIndicesUrl) + ]) + .then(function(results) { + var positionsReference = getMeshes(results[0])[0].positions.toFloatBuffer(); + var positions = getMeshes(results[1])[0].positions.toFloatBuffer(); + expect(positions).toEqual(positionsReference); + }), done).toResolve(); + }); + + it('loads obj with triangle faces', function(done) { + expect(loadObj(objTrianglesUrl) + .then(function(data) { + var mesh = getMeshes(data)[0]; + var primitive = getPrimitives(data)[0]; + expect(mesh.positions.length / 3).toBe(24); + expect(primitive.indices.length).toBe(36); + }), done).toResolve(); + }); + + it('loads obj with triangle faces', function(done) { + expect(loadObj(objTrianglesUrl) + .then(function(data) { + var mesh = getMeshes(data)[0]; + var primitive = getPrimitives(data)[0]; + expect(mesh.positions.length / 3).toBe(24); + expect(primitive.indices.length).toBe(36); + }), done).toResolve(); + }); + + it('loads obj with objects', function(done) { + expect(loadObj(objObjectsUrl) + .then(function(data) { + var nodes = data.nodes; + expect(nodes.length).toBe(3); + expect(nodes[0].name).toBe('CubeBlue'); + expect(nodes[1].name).toBe('CubeGreen'); + expect(nodes[2].name).toBe('CubeRed'); + + var primitives = getPrimitives(data); + expect(primitives.length).toBe(3); + expect(primitives[0].material).toBe('Blue'); + expect(primitives[1].material).toBe('Green'); + expect(primitives[2].material).toBe('Red'); + }), done).toResolve(); + }); + + it('loads obj with groups', function(done) { + expect(loadObj(objGroupsUrl) + .then(function(data) { + var nodes = data.nodes; + expect(nodes.length).toBe(3); + expect(nodes[0].name).toBe('CubeBlue'); + expect(nodes[1].name).toBe('CubeGreen'); + expect(nodes[2].name).toBe('CubeRed'); + + var primitives = getPrimitives(data); + expect(primitives.length).toBe(3); + expect(primitives[0].material).toBe('Blue'); + expect(primitives[1].material).toBe('Green'); + expect(primitives[2].material).toBe('Red'); + }), done).toResolve(); + }); + + it('loads obj with objects and groups', function(done) { + expect(loadObj(objObjectsGroupsUrl) + .then(function(data) { + var nodes = data.nodes; + expect(nodes.length).toBe(3); + expect(nodes[0].name).toBe('CubeBlue'); + expect(nodes[1].name).toBe('CubeGreen'); + expect(nodes[2].name).toBe('CubeRed'); + + var meshes = getMeshes(data); + expect(meshes.length).toBe(3); + expect(meshes[0].name).toBe('CubeBlue_CubeBlue_Blue'); + expect(meshes[1].name).toBe('CubeGreen_CubeGreen_Green'); + expect(meshes[2].name).toBe('CubeRed_CubeRed_Red'); + + var primitives = getPrimitives(data); + expect(primitives.length).toBe(3); + expect(primitives[0].material).toBe('Blue'); + expect(primitives[1].material).toBe('Green'); + expect(primitives[2].material).toBe('Red'); + }), done).toResolve(); + }); + + it('loads obj with usemtl only', function(done) { + expect(loadObj(objUsemtlUrl) + .then(function(data) { + var nodes = data.nodes; + expect(nodes.length).toBe(1); + expect(nodes[0].name).toBe('Node'); // default name + + var meshes = getMeshes(data); + expect(meshes.length).toBe(1); + expect(meshes[0].name).toBe('Node-Mesh'); + + var primitives = getPrimitives(data); + expect(primitives.length).toBe(3); + expect(primitives[0].material).toBe('Blue'); + expect(primitives[1].material).toBe('Green'); + expect(primitives[2].material).toBe('Red'); + }), done).toResolve(); + }); + + it('loads obj with no materials', function(done) { + expect(loadObj(objNoMaterialsUrl) + .then(function(data) { + var nodes = data.nodes; + expect(nodes.length).toBe(1); + expect(nodes[0].name).toBe('Node'); // default name + + var primitives = getPrimitives(data); + expect(primitives.length).toBe(1); + }), done).toResolve(); + }); + + it('loads obj with multiple materials', function(done) { + // The usemtl markers are interleaved, but should condense to just three primitives + expect(loadObj(objMultipleMaterialsUrl) + .then(function(data) { + var nodes = data.nodes; + expect(nodes.length).toBe(1); + + var primitives = getPrimitives(data); + expect(primitives.length).toBe(3); + + expect(primitives[0].indices.length).toBe(12); + expect(primitives[1].indices.length).toBe(12); + expect(primitives[2].indices.length).toBe(12); + expect(primitives[0].material).toBe('Red'); + expect(primitives[1].material).toBe('Green'); + expect(primitives[2].material).toBe('Blue'); + }), done).toResolve(); + }); + + it('loads obj uncleaned', function(done) { + // Obj with extraneous o, g, and usemtl lines + // Also tests handling of o and g lines with the same names + expect(loadObj(objUncleanedUrl) + .then(function(data) { + var nodes = data.nodes; + var meshes = getMeshes(data); + var primitives = getPrimitives(data); + + expect(nodes.length).toBe(1); + expect(meshes.length).toBe(1); + expect(primitives.length).toBe(1); + + expect(nodes[0].name).toBe('Cube'); + expect(meshes[0].name).toBe('Cube_1'); + }), done).toResolve(); + }); + + it('loads obj with multiple mtllibs', function(done) { + expect(loadObj(objMtllibUrl) + .then(function(data) { + var materials = data.materials; + expect(Object.keys(materials).length).toBe(3); + expect(materials.Red.diffuseColor).toEqual([0.64, 0.0, 0.0, 1.0]); + expect(materials.Green.diffuseColor).toEqual([0.0, 0.64, 0.0, 1.0]); + expect(materials.Blue.diffuseColor).toEqual([0.0, 0.0, 0.64, 1.0]); + }), done).toResolve(); + }); + + it('loads obj with missing mtllib', function(done) { + spyOn(console, 'log'); + expect(loadObj(objMissingMtllibUrl) + .then(function(data) { + expect(data.materials).toEqual({}); + }), done).toResolve(); + }); + + it('loads obj with texture', function(done) { + expect(loadObj(objTexturedUrl) + .then(function(data) { + var imagePath = getImagePath(objTexturedUrl, 'cesium.png'); + expect(data.images[imagePath]).toBeDefined(); + expect(data.materials.Material.diffuseColorMap).toEqual(imagePath); + }), done).toResolve(); + }); + + it('loads obj with missing texture', function(done) { + spyOn(console, 'log'); + expect(loadObj(objMissingTextureUrl) + .then(function(data) { + var imagePath = getImagePath(objMissingTextureUrl, 'cesium.png'); + expect(data.images[imagePath]).toBeUndefined(); + expect(data.materials.Material.diffuseColorMap).toEqual(imagePath); + }), done).toResolve(); + }); + + it('loads obj with subdirectories', function(done) { + expect(loadObj(objSubdirectoriesUrl) + .then(function(data) { + var imagePath = getImagePath(objSubdirectoriesUrl, path.join('materials', 'images', 'cesium.png')); + expect(data.images[imagePath]).toBeDefined(); + expect(data.materials.Material.diffuseColorMap).toEqual(imagePath); + }), done).toResolve(); + }); + + it('loads obj with complex material', function(done) { + expect(loadObj(objComplexMaterialUrl) + .then(function(data) { + var images = data.images; + expect(Object.keys(images).length).toBe(4); // Only ambient, diffuse, emission, and specular maps are supported by the converter + }), done).toResolve(); + }); + + it('does not process file with invalid contents', function(done) { + expect(loadObj(objInvalidContentsUrl), done).toRejectWith(RuntimeError); + }); + + it('throw when reading invalid file', function(done) { + expect(loadObj(objInvalidUrl), done).toRejectWith(Error); + }); +}); From 5d0511b6a088372929f1001b6961f2da6a70a399 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 14 Mar 2017 10:56:44 -0400 Subject: [PATCH 13/69] Added .editorconfig --- .editorconfig | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2536d66 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false From d46d58fdc5b00b4a0398deafd12c443bf7c3ea88 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 14 Mar 2017 12:06:50 -0400 Subject: [PATCH 14/69] Set diffuse to black when constant lighting is used --- lib/gltf.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/gltf.js b/lib/gltf.js index c0b0189..d6d9b86 100644 --- a/lib/gltf.js +++ b/lib/gltf.js @@ -90,6 +90,7 @@ function createGltf(objData) { if (!hasNormals) { // Constant technique only factors in ambient and emission sources - set emission to diffuse emission = diffuse; + diffuse = [0, 0, 0, 1]; } var technique = hasNormals ? (hasSpecular ? 'PHONG' : 'LAMBERT') : 'CONSTANT'; From 998dcfe6496dda17e5c6a00d0783928a564537da Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 14 Mar 2017 13:26:12 -0400 Subject: [PATCH 15/69] Fix saving .bin --- lib/convert.js | 4 +++- lib/gltf.js | 11 ++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/convert.js b/lib/convert.js index 6088198..60799ba 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -110,11 +110,13 @@ function saveExternalBuffer(gltf, gltfPath) { return Promise.resolve(gltf); } + var binary = buffer.extras._obj2gltf.binary; + delete buffer.extras; var bufferName = path.basename(gltfPath, path.extname(gltfPath)); var bufferUri = bufferName + '.bin'; buffer.uri = bufferUri; var bufferPath = path.join(path.dirname(gltfPath), bufferUri); - return fxExtraOutputFile(bufferPath, buffer) + return fxExtraOutputFile(bufferPath, binary) .then(function() { return gltf; }); diff --git a/lib/gltf.js b/lib/gltf.js index d6d9b86..8efea35 100644 --- a/lib/gltf.js +++ b/lib/gltf.js @@ -304,7 +304,7 @@ function createGltf(objData) { var indexBuffer = Buffer.concat(indexBuffers); var buffer = Buffer.concat([vertexBuffer, indexBuffer]); - // Buffers larger than ~192MB cannot be base64 encoded due to a NodeJS limitation. Instead the buffer will be saved to a .bin file. 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 var bufferUri; if (buffer.length <= 201326580) { bufferUri = 'data:application/octet-stream;base64,' + buffer.toString('base64'); @@ -329,5 +329,14 @@ function createGltf(objData) { target : WebGLConstants.ELEMENT_ARRAY_BUFFER }; + // Save the binary to be outputted as a .bin file in convert.js. + if (!defined(bufferUri)) { + gltf.buffers[bufferId].extras = { + _obj2gltf : { + binary : buffer + } + }; + } + return gltf; } From cf27c8b69dde8df51f4775ba75d9edda00878cdb Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 14 Mar 2017 14:40:33 -0400 Subject: [PATCH 16/69] Better buffer handling --- lib/gltf.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/gltf.js b/lib/gltf.js index 8efea35..b24b887 100644 --- a/lib/gltf.js +++ b/lib/gltf.js @@ -144,9 +144,9 @@ function createGltf(objData) { } var vertexBuffers = []; - var vertexByteOffset = 0; + var vertexBufferByteOffset = 0; var indexBuffers = []; - var indexBuffersByteOffset = 0; + var indexBufferByteOffset = 0; var accessorCount = 0; function addVertexAttribute(array, components) { @@ -157,7 +157,7 @@ function createGltf(objData) { var type = (components === 3 ? 'VEC3' : 'VEC2'); var accessor = { bufferView : vertexBufferViewId, - byteOffset : vertexByteOffset, + byteOffset : vertexBufferByteOffset, byteStride : 0, componentType : WebGLConstants.FLOAT, count : count, @@ -166,7 +166,7 @@ function createGltf(objData) { type : type }; - vertexByteOffset += buffer.length; + vertexBufferByteOffset += buffer.length; vertexBuffers.push(buffer); var accessorId = 'accessor_' + accessorCount++; gltf.accessors[accessorId] = accessor; @@ -180,7 +180,7 @@ function createGltf(objData) { var minMax = array.getMinMax(1); var accessor = { bufferView : indexBufferViewId, - byteOffset : indexBuffersByteOffset, + byteOffset : indexBufferByteOffset, byteStride : 0, componentType : componentType, count : length, @@ -189,7 +189,7 @@ function createGltf(objData) { type : 'SCALAR' }; - indexBuffersByteOffset += buffer.length; + indexBufferByteOffset += buffer.length; indexBuffers.push(buffer); var accessorId = 'accessor_' + accessorCount++; @@ -300,9 +300,9 @@ function createGltf(objData) { } } - var vertexBuffer = Buffer.concat(vertexBuffers); - var indexBuffer = Buffer.concat(indexBuffers); - var buffer = Buffer.concat([vertexBuffer, indexBuffer]); + var buffers = []; + buffers = buffers.concat(vertexBuffers, indexBuffers); + var buffer = Buffer.concat(buffers); // Buffers larger than ~192MB cannot be base64 encoded due to a NodeJS limitation. Source: https://github.com/nodejs/node/issues/4266 var bufferUri; @@ -317,15 +317,15 @@ function createGltf(objData) { gltf.bufferViews[vertexBufferViewId] = { buffer : bufferId, - byteLength : vertexBuffer.length, + byteLength : vertexBufferByteOffset, byteOffset : 0, target : WebGLConstants.ARRAY_BUFFER }; gltf.bufferViews[indexBufferViewId] = { buffer : bufferId, - byteLength : indexBuffer.length, - byteOffset : vertexBuffer.length, + byteLength : indexBufferByteOffset, + byteOffset : vertexBufferByteOffset, target : WebGLConstants.ELEMENT_ARRAY_BUFFER }; From 41956dd70d1ab98d180a7944fedaf82592fc97a7 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 14 Mar 2017 16:42:42 -0400 Subject: [PATCH 17/69] Output separate resources even when --bypassPipeline is set --- README.md | 2 +- bin/obj2gltf.js | 4 +- lib/convert.js | 90 ++++++++++++++++++++++++++++++---------- lib/gltf.js | 24 ++++++----- lib/image.js | 3 +- specs/lib/convertSpec.js | 6 ++- specs/lib/gltfSpec.js | 15 +++++++ 7 files changed, 105 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 6c53bbe..f73505c 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Using obj2gltf as a command-line tool: |`-i`|Path to the obj file.| :white_check_mark: Yes| |`-o`|Path of the converted glTF file.|No| |`-b`|Save as binary glTF.|No, default `false`| -|`-s`|Writes out separate geometry/animation data files, shader files, and textures instead of embedding them in the glTF file.|No, default `false`| +|`-s`|Writes out separate geometry data files, shader files, and textures instead of embedding them in the glTF file.|No, default `false`| |`-t`|Write out separate textures only.|No, default `false`| |`-c`|Quantize positions, compress texture coordinates, and oct-encode normals.|No, default `false`| |`-z`|Use the optimization stages in the glTF pipeline.|No, default `false`| diff --git a/bin/obj2gltf.js b/bin/obj2gltf.js index 6cfaf40..aa4e4c4 100644 --- a/bin/obj2gltf.js +++ b/bin/obj2gltf.js @@ -37,11 +37,11 @@ var argv = yargs }, 'separate': { alias: 's', - describe: 'Write separate geometry/animation data files, shader files, and textures instead of embedding them in the glTF.', + describe: 'Write separate geometry data files, shader files, and textures instead of embedding them in the glTF.', type: 'boolean', default: false }, - 'separateTexture': { + 'separateTextures': { alias: 't', describe: 'Write out separate textures only.', type: 'boolean', diff --git a/lib/convert.js b/lib/convert.js index 60799ba..cc198fa 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -7,7 +7,7 @@ var Promise = require('bluebird'); var createGltf = require('./gltf'); var loadObj = require('./obj'); -var fxExtraOutputFile = Promise.promisify(fsExtra.outputFile); +var fsExtraOutputFile = Promise.promisify(fsExtra.outputFile); var fsExtraOutputJson = Promise.promisify(fsExtra.outputJson); var defaultValue = Cesium.defaultValue; @@ -23,7 +23,7 @@ module.exports = convert; * @param {String} gltfPath Path of the converted glTF file. * @param {Object} [options] An object with the following properties: * @param {Boolean} [options.binary=false] Save as binary glTF. - * @param {Boolean} [options.separate=false] Writes out separate geometry/animation data files, shader files, and textures instead of embedding them in the glTF. + * @param {Boolean} [options.separate=false] Writes out separate geometry data files, shader files, and textures instead of embedding them in the glTF. * @param {Boolean} [options.separateTextures=false] Write out separate textures only. * @param {Boolean} [options.compress=false] Quantize positions, compress texture coordinates, and oct-encode normals. * @param {Boolean} [options.optimize=false] Use the optimization stages in the glTF pipeline. @@ -38,7 +38,7 @@ function convert(objPath, gltfPath, options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var binary = defaultValue(options.binary, false); var separate = defaultValue(options.separate, false); - var separateTextures = defaultValue(options.separateTextures, false); + var separateTextures = defaultValue(options.separateTextures, false) || separate; var compress = defaultValue(options.compress, false); var optimize = defaultValue(options.optimize, false); var optimizeForCesium = defaultValue(options.optimizeForCesium, false); @@ -70,7 +70,7 @@ function convert(objPath, gltfPath, options) { basePath : basePath, binary : binary, embed : !separate, - embedImage : !separate && !separateTextures, + embedImage : !separateTextures, quantize : compress, compressTextureCoordinates : compress, encodeNormals : compress, @@ -86,7 +86,7 @@ function convert(objPath, gltfPath, options) { return createGltf(objData); }) .then(function(gltf) { - return saveExternalBuffer(gltf, gltfPath); + return writeSeparateResources(gltf, gltfPath, separate, separateTextures); }) .then(function(gltf) { if (bypassPipeline) { @@ -97,6 +97,63 @@ function convert(objPath, gltfPath, options) { }); } +function deleteExtras(gltf) { + var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; + delete buffer.extras; + + var images = gltf.images; + for (var id in images) { + if (images.hasOwnProperty(id)) { + var image = images[id]; + delete image.extras; + } + } +} + +function writeSeparateBuffer(gltf, gltfPath) { + var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; + var source = buffer.extras._obj2gltf.source; + var bufferName = path.basename(gltfPath, path.extname(gltfPath)); + var bufferUri = bufferName + '.bin'; + buffer.uri = bufferUri; + var bufferPath = path.join(path.dirname(gltfPath), bufferUri); + return convert._outputFile(bufferPath, source); +} + +function writeSeparateTextures(gltf, gltfPath) { + var promises = []; + var images = gltf.images; + for (var id in images) { + if (images.hasOwnProperty(id)) { + var image = images[id]; + var extras = image.extras._obj2gltf; + var imageUri = image.name + extras.extension; + image.uri = imageUri; + var imagePath = path.join(path.dirname(gltfPath), imageUri); + promises.push(convert._outputFile(imagePath, extras.source)); + } + } + return Promise.all(promises); +} + +function writeSeparateResources(gltf, gltfPath, separate, separateTextures) { + var promises = []; + var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; + + if (separate || !defined(buffer.uri)) { + promises.push(writeSeparateBuffer(gltf, gltfPath)); + } + if (separateTextures) { + promises.push(writeSeparateTextures(gltf, gltfPath)); + } + + deleteExtras(gltf); + return Promise.all(promises) + .then(function() { + return gltf; + }); +} + /** * Exposed for testing * @@ -104,20 +161,9 @@ function convert(objPath, gltfPath, options) { */ convert._outputJson = fsExtraOutputJson; -function saveExternalBuffer(gltf, gltfPath) { - var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; - if (defined(buffer.uri)) { - return Promise.resolve(gltf); - } - - var binary = buffer.extras._obj2gltf.binary; - delete buffer.extras; - var bufferName = path.basename(gltfPath, path.extname(gltfPath)); - var bufferUri = bufferName + '.bin'; - buffer.uri = bufferUri; - var bufferPath = path.join(path.dirname(gltfPath), bufferUri); - return fxExtraOutputFile(bufferPath, binary) - .then(function() { - return gltf; - }); -} +/** + * Exposed for testing + * + * @private + */ +convert._outputFile = fsExtraOutputFile; diff --git a/lib/gltf.js b/lib/gltf.js index b24b887..0fcee9f 100644 --- a/lib/gltf.js +++ b/lib/gltf.js @@ -130,7 +130,13 @@ function createGltf(objData) { gltf.images[imageId] = { name : imageId, - uri : image.uri + uri : image.uri, + extras : { + _obj2gltf : { + source : image.data, + extension : image.extension + } + } }; gltf.textures[textureId] = { format : image.format, @@ -312,7 +318,12 @@ function createGltf(objData) { gltf.buffers[bufferId] = { byteLength : buffer.byteLength, - uri : bufferUri + uri : bufferUri, + extras : { + _obj2gltf : { + source : buffer + } + } }; gltf.bufferViews[vertexBufferViewId] = { @@ -329,14 +340,5 @@ function createGltf(objData) { target : WebGLConstants.ELEMENT_ARRAY_BUFFER }; - // Save the binary to be outputted as a .bin file in convert.js. - if (!defined(bufferUri)) { - gltf.buffers[bufferId].extras = { - _obj2gltf : { - binary : buffer - } - }; - } - return gltf; } diff --git a/lib/image.js b/lib/image.js index 9d2ad74..7f92a47 100644 --- a/lib/image.js +++ b/lib/image.js @@ -30,7 +30,8 @@ function loadImage(imagePath) { channels : 3, data : data, uri : uri, - format : getFormat(3) + format : getFormat(3), + extension : extension }; if (extension === '.png') { diff --git a/specs/lib/convertSpec.js b/specs/lib/convertSpec.js index 401cb83..f3da1f2 100644 --- a/specs/lib/convertSpec.js +++ b/specs/lib/convertSpec.js @@ -47,7 +47,8 @@ describe('convert', function() { }); it('sets options', function(done) { - var spy = spyOn(GltfPipeline, 'processJSONToDisk'); + var spy1 = spyOn(GltfPipeline, 'processJSONToDisk'); + var spy2 = spyOn(convert, '_outputFile'); var textureCompressionOptions = { format : 'dxt1', quality : 10 @@ -66,7 +67,7 @@ describe('convert', function() { expect(convert(objPath, gltfPath, options) .then(function() { - var args = spy.calls.first().args; + var args = spy1.calls.first().args; var options = args[2]; expect(options).toEqual({ createDirectory : false, @@ -83,6 +84,7 @@ describe('convert', function() { textureCompressionOptions : textureCompressionOptions, preserve : false }); + expect(spy2.calls.count()).toBe(2); // Saves out .png and .bin }), done).toResolve(); }); diff --git a/specs/lib/gltfSpec.js b/specs/lib/gltfSpec.js index 3ff7714..6e415b7 100644 --- a/specs/lib/gltfSpec.js +++ b/specs/lib/gltfSpec.js @@ -19,6 +19,19 @@ var groupGltfUrl = 'specs/data/box-objects-groups-materials/box-objects-groups-m var diffuseTextureUrl = 'specs/data/box-textured/cesium.png'; var transparentDiffuseTextureUrl = 'specs/data/box-complex-material/diffuse.png'; +function deleteExtras(gltf) { + var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; + delete buffer.extras; + + var images = gltf.images; + for (var id in images) { + if (images.hasOwnProperty(id)) { + var image = images[id]; + delete image.extras; + } + } +} + describe('gltf', function() { var boxObjData; var groupObjData; @@ -59,12 +72,14 @@ describe('gltf', function() { it('simple gltf', function() { var objData = clone(boxObjData, true); var gltf = createGltf(objData); + deleteExtras(gltf); expect(gltf).toEqual(boxGltf); }); it('multiple nodes, meshes, and primitives', function() { var objData = clone(groupObjData, true); var gltf = createGltf(objData); + deleteExtras(gltf); expect(gltf).toEqual(groupGltf); expect(Object.keys(gltf.materials).length).toBe(3); From 5f6d84581fdbf47b4b075cf03645999e8b77c076 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 15 Mar 2017 13:05:32 -0400 Subject: [PATCH 18/69] Check for transparent pixels and fix for khr_materials_common --- LICENSE.md | 25 ++++++++++++++++++ lib/gltf.js | 7 +++++ lib/image.js | 21 ++++++++++++--- package.json | 1 + specs/data/box-complex-material/bump.png | Bin 4720 -> 4998 bytes .../box-objects-groups-materials.gltf | 6 +++++ specs/data/box/box.gltf | 2 ++ specs/lib/imageSpec.js | 19 ++++++++----- 8 files changed, 71 insertions(+), 10 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 82de349..2a7cb66 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -110,6 +110,31 @@ https://www.npmjs.com/package/gltf-pipeline > > Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +### pngjs + +https://www.npmjs.com/package/pngjs + +> pngjs2 original work Copyright (c) 2015 Luke Page & Original Contributors +> pngjs derived work Copyright (c) 2012 Kuba Niegowski +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + ### yargs https://www.npmjs.com/package/yargs diff --git a/lib/gltf.js b/lib/gltf.js index 0fcee9f..c84df40 100644 --- a/lib/gltf.js +++ b/lib/gltf.js @@ -93,11 +93,18 @@ function createGltf(objData) { diffuse = [0, 0, 0, 1]; } + // It's not completely clear whether transparent and doubleSided belong under values or KHR_materials_common + // Put under both for now to handle both situations. + // https://github.com/KhronosGroup/glTF/tree/master/extensions/Khronos/KHR_materials_common + // https://github.com/KhronosGroup/glTF/issues/632 + var technique = hasNormals ? (hasSpecular ? 'PHONG' : 'LAMBERT') : 'CONSTANT'; return { extensions : { KHR_materials_common : { technique : technique, + transparent : transparent, + doubleSided : doubleSided, values : { ambient : ambient, diffuse : diffuse, diff --git a/lib/image.js b/lib/image.js index 7f92a47..2387a6a 100644 --- a/lib/image.js +++ b/lib/image.js @@ -2,6 +2,7 @@ var Cesium = require('cesium'); var fs = require('fs-extra'); var path = require('path'); +var PNG = require('pngjs').PNG; var Promise = require('bluebird'); var fsReadFile = Promise.promisify(fs.readFile); @@ -27,7 +28,6 @@ function loadImage(imagePath) { var info = { transparent : false, - channels : 3, data : data, uri : uri, format : getFormat(3), @@ -38,9 +38,12 @@ function loadImage(imagePath) { // Color type is encoded in the 25th bit of the png var colorType = data[25]; var channels = getChannels(colorType); - info.channels = channels; - info.transparent = (channels === 4); info.format = getFormat(channels); + + if (channels === 4) { + // Need to do a finer grained check over the pixels to see if the image is actually transparent + info.transparent = isTransparent(data); + } } return info; @@ -51,6 +54,18 @@ function loadImage(imagePath) { }); } +function isTransparent(data) { + var decoded = PNG.sync.read(data); + var pixels = decoded.data; + var pixelsLength = decoded.width * decoded.height; + for (var i = 0; i < pixelsLength; ++i) { + if (pixels[i * 4 + 3] < 255) { + return true; + } + } + return false; +} + function getChannels(colorType) { switch (colorType) { case 0: // greyscale diff --git a/package.json b/package.json index bdce320..53e3598 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "event-stream": "^3.3.4", "fs-extra": "^2.0.0", "gltf-pipeline": "^0.1.0-alpha11", + "pngjs": "^3.0.1", "yargs": "^7.0.1" }, "devDependencies": { diff --git a/specs/data/box-complex-material/bump.png b/specs/data/box-complex-material/bump.png index fcc5ba8ca8f1cee6a086ab43efa054578e6a670a..16ec3a936601922682f2efadd6f19a39d80c7150 100644 GIT binary patch literal 4998 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 literal 4720 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 Date: Wed, 15 Mar 2017 13:19:47 -0400 Subject: [PATCH 19/69] Remove typings --- .gitignore | 3 --- .idea/jsLibraryMappings.xml | 8 -------- .idea/libraries/typings.xml | 13 ------------- .idea/obj2gltf.iml | 1 - .npmignore | 2 -- package.json | 4 +--- typings.json | 16 ---------------- 7 files changed, 1 insertion(+), 46 deletions(-) delete mode 100644 .idea/jsLibraryMappings.xml delete mode 100644 .idea/libraries/typings.xml delete mode 100644 typings.json diff --git a/.gitignore b/.gitignore index b04cad6..8e46cf7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,6 @@ node_modules npm-debug.log -# TypeScript definitions -typings - # WebStorm user-specific .idea/workspace.xml .idea/tasks.xml diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml deleted file mode 100644 index 45d7418..0000000 --- a/.idea/jsLibraryMappings.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/typings.xml b/.idea/libraries/typings.xml deleted file mode 100644 index 9e440e4..0000000 --- a/.idea/libraries/typings.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/obj2gltf.iml b/.idea/obj2gltf.iml index cc19882..bbdc004 100644 --- a/.idea/obj2gltf.iml +++ b/.idea/obj2gltf.iml @@ -7,6 +7,5 @@ - \ No newline at end of file diff --git a/.npmignore b/.npmignore index 735304f..202de55 100644 --- a/.npmignore +++ b/.npmignore @@ -5,10 +5,8 @@ /specs /test /output -/typings .jshintrc .npmignore .travis.yml gulpfile.js -typings.json *.tgz diff --git a/package.json b/package.json index 53e3598..7a1516e 100644 --- a/package.json +++ b/package.json @@ -43,11 +43,9 @@ "jshint": "^2.9.4", "jshint-stylish": "^2.2.1", "open": "^0.0.5", - "requirejs": "^2.3.3", - "typings": "^2.1.0" + "requirejs": "^2.3.3" }, "scripts": { - "prepublish": "typings install", "jsdoc": "jsdoc ./lib -R ./README.md -d doc", "jsHint": "gulp jsHint", "jsHint-watch": "gulp jsHint-watch", diff --git a/typings.json b/typings.json deleted file mode 100644 index 1eaaaa8..0000000 --- a/typings.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "dependencies": { - "async": "registry:npm/async#2.0.1+20160815105832", - "bluebird": "registry:npm/bluebird#3.4.1+20160909132857", - "gulp": "registry:npm/gulp#4.0.0-alpha.2+20160817105618", - "requirejs": "registry:npm/requirejs#2.2.0+20160319062357", - "yargs": "registry:npm/yargs#5.0.0+20160907000723" - }, - "globalDependencies": { - "byline": "registry:dt/byline#4.2.1+20161006132146", - "fs-extra": "registry:dt/fs-extra#0.0.0+20161004190449", - "istanbul": "registry:dt/istanbul#0.4.0+20160316155526", - "jasmine": "registry:dt/jasmine#2.5.0+20161003201800", - "open": "registry:dt/open#0.0.3+20160316155526" - } -} From dea55fec0f51598a19c4a8f0d023f16e04869c6d Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 17 Mar 2017 11:40:54 -0400 Subject: [PATCH 20/69] Updates --- lib/obj.js | 24 ++++++++++++------------ specs/lib/objSpec.js | 10 ---------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/lib/obj.js b/lib/obj.js index abe024d..794b9a9 100644 --- a/lib/obj.js +++ b/lib/obj.js @@ -113,8 +113,8 @@ function loadObj(objPath) { var primitives = mesh.primitives; var primitivesLength = primitives.length; for (var i = 0; i < primitivesLength; ++i) { - primitive = primitives[i]; // Sets the active primitive in case of returning early - if (primitive.material === material) { + if (primitives[i].material === material) { + primitive = primitives[i]; return; } } @@ -331,25 +331,25 @@ function loadImages(imagePaths) { } function getImagePaths(materials) { - var imagePaths = []; + var imagePaths = {}; for (var name in materials) { if (materials.hasOwnProperty(name)) { var material = materials[name]; - if (defined(material.ambientColorMap) && imagePaths.indexOf(material.ambientColorMap) === -1) { - imagePaths.push(material.ambientColorMap); + if (defined(material.ambientColorMap)) { + imagePaths[material.ambientColorMap] = true; } - if (defined(material.diffuseColorMap) && imagePaths.indexOf(material.diffuseColorMap) === -1) { - imagePaths.push(material.diffuseColorMap); + if (defined(material.diffuseColorMap)) { + imagePaths[material.diffuseColorMap] = true; } - if (defined(material.emissionColorMap) && imagePaths.indexOf(material.emissionColorMap) === -1) { - imagePaths.push(material.emissionColorMap); + if (defined(material.emissionColorMap)) { + imagePaths[material.emissionColorMap] = true; } - if (defined(material.specularColorMap) && imagePaths.indexOf(material.specularColorMap) === -1) { - imagePaths.push(material.specularColorMap); + if (defined(material.specularColorMap)) { + imagePaths[material.specularColorMap] = true; } } } - return imagePaths; + return Object.keys(imagePaths); } function removeEmptyPrimitives(primitives) { diff --git a/specs/lib/objSpec.js b/specs/lib/objSpec.js index 89f0f91..b064bdc 100644 --- a/specs/lib/objSpec.js +++ b/specs/lib/objSpec.js @@ -128,16 +128,6 @@ describe('obj', function() { }), done).toResolve(); }); - it('loads obj with triangle faces', function(done) { - expect(loadObj(objTrianglesUrl) - .then(function(data) { - var mesh = getMeshes(data)[0]; - var primitive = getPrimitives(data)[0]; - expect(mesh.positions.length / 3).toBe(24); - expect(primitive.indices.length).toBe(36); - }), done).toResolve(); - }); - it('loads obj with objects', function(done) { expect(loadObj(objObjectsUrl) .then(function(data) { From 0bf726cea7a61722841c5eab31a12d4a90f92e66 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 17 Mar 2017 15:44:01 -0400 Subject: [PATCH 21/69] Better handling of encoding base64 uris --- lib/convert.js | 68 +-------------------- lib/gltf.js | 10 +--- lib/image.js | 19 +----- lib/writeUris.js | 126 +++++++++++++++++++++++++++++++++++++++ package.json | 1 + specs/lib/convertSpec.js | 4 +- specs/lib/gltfSpec.js | 21 ++----- specs/lib/imageSpec.js | 28 ++++----- 8 files changed, 152 insertions(+), 125 deletions(-) create mode 100644 lib/writeUris.js diff --git a/lib/convert.js b/lib/convert.js index cc198fa..c34d8e4 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -6,8 +6,8 @@ var path = require('path'); var Promise = require('bluebird'); var createGltf = require('./gltf'); var loadObj = require('./obj'); +var writeUris = require('./writeUris'); -var fsExtraOutputFile = Promise.promisify(fsExtra.outputFile); var fsExtraOutputJson = Promise.promisify(fsExtra.outputJson); var defaultValue = Cesium.defaultValue; @@ -86,7 +86,7 @@ function convert(objPath, gltfPath, options) { return createGltf(objData); }) .then(function(gltf) { - return writeSeparateResources(gltf, gltfPath, separate, separateTextures); + return writeUris(gltf, gltfPath, separate, separateTextures); }) .then(function(gltf) { if (bypassPipeline) { @@ -97,73 +97,9 @@ function convert(objPath, gltfPath, options) { }); } -function deleteExtras(gltf) { - var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; - delete buffer.extras; - - var images = gltf.images; - for (var id in images) { - if (images.hasOwnProperty(id)) { - var image = images[id]; - delete image.extras; - } - } -} - -function writeSeparateBuffer(gltf, gltfPath) { - var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; - var source = buffer.extras._obj2gltf.source; - var bufferName = path.basename(gltfPath, path.extname(gltfPath)); - var bufferUri = bufferName + '.bin'; - buffer.uri = bufferUri; - var bufferPath = path.join(path.dirname(gltfPath), bufferUri); - return convert._outputFile(bufferPath, source); -} - -function writeSeparateTextures(gltf, gltfPath) { - var promises = []; - var images = gltf.images; - for (var id in images) { - if (images.hasOwnProperty(id)) { - var image = images[id]; - var extras = image.extras._obj2gltf; - var imageUri = image.name + extras.extension; - image.uri = imageUri; - var imagePath = path.join(path.dirname(gltfPath), imageUri); - promises.push(convert._outputFile(imagePath, extras.source)); - } - } - return Promise.all(promises); -} - -function writeSeparateResources(gltf, gltfPath, separate, separateTextures) { - var promises = []; - var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; - - if (separate || !defined(buffer.uri)) { - promises.push(writeSeparateBuffer(gltf, gltfPath)); - } - if (separateTextures) { - promises.push(writeSeparateTextures(gltf, gltfPath)); - } - - deleteExtras(gltf); - return Promise.all(promises) - .then(function() { - return gltf; - }); -} - /** * Exposed for testing * * @private */ convert._outputJson = fsExtraOutputJson; - -/** - * Exposed for testing - * - * @private - */ -convert._outputFile = fsExtraOutputFile; diff --git a/lib/gltf.js b/lib/gltf.js index c84df40..869276e 100644 --- a/lib/gltf.js +++ b/lib/gltf.js @@ -137,10 +137,9 @@ function createGltf(objData) { gltf.images[imageId] = { name : imageId, - uri : image.uri, extras : { _obj2gltf : { - source : image.data, + source : image.source, extension : image.extension } } @@ -317,15 +316,8 @@ function createGltf(objData) { buffers = buffers.concat(vertexBuffers, indexBuffers); var buffer = Buffer.concat(buffers); - // Buffers larger than ~192MB cannot be base64 encoded due to a NodeJS limitation. Source: https://github.com/nodejs/node/issues/4266 - var bufferUri; - if (buffer.length <= 201326580) { - bufferUri = 'data:application/octet-stream;base64,' + buffer.toString('base64'); - } - gltf.buffers[bufferId] = { byteLength : buffer.byteLength, - uri : bufferUri, extras : { _obj2gltf : { source : buffer diff --git a/lib/image.js b/lib/image.js index 2387a6a..4974d9d 100644 --- a/lib/image.js +++ b/lib/image.js @@ -23,14 +23,11 @@ function loadImage(imagePath) { return fsReadFile(imagePath) .then(function(data) { var extension = path.extname(imagePath); - var uriType = getUriType(extension); - var uri = uriType + ';base64,' + data.toString('base64'); var info = { transparent : false, - data : data, - uri : uri, format : getFormat(3), + source : data, extension : extension }; @@ -81,20 +78,6 @@ function getChannels(colorType) { } } -function getUriType(extension) { - switch (extension) { - case '.png': - return 'data:image/png'; - case '.jpg': - case '.jpeg': - return 'data:image/jpeg'; - case '.gif': - return 'data:image/gif'; - default: - return 'data:image/' + extension.slice(1); - } -} - function getFormat(channels) { switch (channels) { case 1: diff --git a/lib/writeUris.js b/lib/writeUris.js new file mode 100644 index 0000000..286ed72 --- /dev/null +++ b/lib/writeUris.js @@ -0,0 +1,126 @@ +'use strict'; +var fsExtra = require('fs-extra'); +var mime = require('mime'); +var path = require('path'); +var Promise = require('bluebird'); + +var fsExtraOutputFile = Promise.promisify(fsExtra.outputFile); + +module.exports = writeUris; + +/** + * Write glTF resources as embedded data uris or external files. + * + * @param {Object} gltf The glTF asset. + * @param {String} gltfPath Path where the glTF will be saved. + * @param {Boolean} separateBuffers Writes out separate buffers. + * @param {Boolean} separateTextures Writes out separate textures. + * @returns {Promise} A promise that resolves to the glTF asset. + * + * @private + */ +function writeUris(gltf, gltfPath, separateBuffers, separateTextures) { + var promises = []; + + var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; + var bufferByteLength = buffer.extras._obj2gltf.source.length; + + var texturesByteLength = 0; + var images = gltf.images; + for (var id in images) { + if (images.hasOwnProperty(id)) { + texturesByteLength += images[id].extras._obj2gltf.source.length; + } + } + + // Buffers larger than ~192MB cannot be base64 encoded due to a NodeJS limitation. Source: https://github.com/nodejs/node/issues/4266 + var exceedsMaximum = (texturesByteLength + bufferByteLength > 201326580); + + if (exceedsMaximum) { + console.log('Buffers and textures are too large to encode in the glTF, saving as separate resources.'); + } + + if (separateBuffers || exceedsMaximum) { + promises.push(writeSeparateBuffer(gltf, gltfPath)); + } else { + writeEmbeddedBuffer(gltf); + } + + if (separateTextures || exceedsMaximum) { + promises.push(writeSeparateTextures(gltf, gltfPath)); + } else { + writeEmbeddedTextures(gltf); + } + + deleteExtras(gltf); + + return Promise.all(promises) + .then(function() { + return gltf; + }); +} + +function deleteExtras(gltf) { + var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; + delete buffer.extras; + + var images = gltf.images; + for (var id in images) { + if (images.hasOwnProperty(id)) { + var image = images[id]; + delete image.extras; + } + } +} + +function writeSeparateBuffer(gltf, gltfPath) { + var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; + var source = buffer.extras._obj2gltf.source; + var bufferName = path.basename(gltfPath, path.extname(gltfPath)); + var bufferUri = bufferName + '.bin'; + buffer.uri = bufferUri; + var bufferPath = path.join(path.dirname(gltfPath), bufferUri); + return writeUris._outputFile(bufferPath, source); +} + +function writeSeparateTextures(gltf, gltfPath) { + var promises = []; + var images = gltf.images; + for (var id in images) { + if (images.hasOwnProperty(id)) { + var image = images[id]; + var extras = image.extras._obj2gltf; + var imageUri = image.name + extras.extension; + image.uri = imageUri; + var imagePath = path.join(path.dirname(gltfPath), imageUri); + promises.push(writeUris._outputFile(imagePath, extras.source)); + } + } + return Promise.all(promises); +} + +function writeEmbeddedBuffer(gltf) { + var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; + var source = buffer.extras._obj2gltf.source; + buffer.uri = 'data:application/octet-stream;base64,' + source.toString('base64'); +} + +function writeEmbeddedTextures(gltf) { + var promises = []; + var images = gltf.images; + for (var id in images) { + if (images.hasOwnProperty(id)) { + var image = images[id]; + var extras = image.extras._obj2gltf; + image.uri = 'data:' + mime.lookup(extras.extension) + ';base64,' + extras.source.toString('base64'); + } + } + return Promise.all(promises); +} + +/** + * Exposed for testing + * + * @private + */ +writeUris._outputFile = fsExtraOutputFile; diff --git a/package.json b/package.json index 7a1516e..e1df602 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "event-stream": "^3.3.4", "fs-extra": "^2.0.0", "gltf-pipeline": "^0.1.0-alpha11", + "mime": "^1.3.4", "pngjs": "^3.0.1", "yargs": "^7.0.1" }, diff --git a/specs/lib/convertSpec.js b/specs/lib/convertSpec.js index f3da1f2..a409911 100644 --- a/specs/lib/convertSpec.js +++ b/specs/lib/convertSpec.js @@ -1,8 +1,8 @@ 'use strict'; -var fsExtra = require('fs-extra'); var GltfPipeline = require('gltf-pipeline').Pipeline; var path = require('path'); var convert = require('../../lib/convert'); +var writeUris = require('../../lib/writeUris'); var objPath = 'specs/data/box-textured/box-textured.obj'; var gltfPath = 'specs/data/box-textured/box-textured.gltf'; @@ -48,7 +48,7 @@ describe('convert', function() { it('sets options', function(done) { var spy1 = spyOn(GltfPipeline, 'processJSONToDisk'); - var spy2 = spyOn(convert, '_outputFile'); + var spy2 = spyOn(writeUris, '_outputFile'); var textureCompressionOptions = { format : 'dxt1', quality : 10 diff --git a/specs/lib/gltfSpec.js b/specs/lib/gltfSpec.js index 6e415b7..260a116 100644 --- a/specs/lib/gltfSpec.js +++ b/specs/lib/gltfSpec.js @@ -7,6 +7,7 @@ var clone = require('../../lib/clone.js'); var createGltf = require('../../lib/gltf.js'); var loadImage = require('../../lib/image.js'); var loadObj = require('../../lib/obj.js'); +var writeUris = require('../../lib/writeUris.js'); var WebGLConstants = Cesium.WebGLConstants; @@ -19,19 +20,6 @@ var groupGltfUrl = 'specs/data/box-objects-groups-materials/box-objects-groups-m var diffuseTextureUrl = 'specs/data/box-textured/cesium.png'; var transparentDiffuseTextureUrl = 'specs/data/box-complex-material/diffuse.png'; -function deleteExtras(gltf) { - var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; - delete buffer.extras; - - var images = gltf.images; - for (var id in images) { - if (images.hasOwnProperty(id)) { - var image = images[id]; - delete image.extras; - } - } -} - describe('gltf', function() { var boxObjData; var groupObjData; @@ -72,14 +60,14 @@ describe('gltf', function() { it('simple gltf', function() { var objData = clone(boxObjData, true); var gltf = createGltf(objData); - deleteExtras(gltf); + writeUris(gltf, boxGltfUrl, false, false); expect(gltf).toEqual(boxGltf); }); it('multiple nodes, meshes, and primitives', function() { var objData = clone(groupObjData, true); var gltf = createGltf(objData); - deleteExtras(gltf); + writeUris(gltf, groupGltfUrl, false, false); expect(gltf).toEqual(groupGltf); expect(Object.keys(gltf.materials).length).toBe(3); @@ -141,7 +129,8 @@ describe('gltf', function() { expect(image).toBeDefined(); expect(image.name).toBe('cesium'); - expect(image.uri.indexOf('data:image/png;base64,') >= 0).toBe(true); + expect(image.extras._obj2gltf.source).toBeDefined(); + expect(image.extras._obj2gltf.extension).toBe('.png'); expect(gltf.samplers.sampler).toEqual({ magFilter : WebGLConstants.LINEAR, diff --git a/specs/lib/imageSpec.js b/specs/lib/imageSpec.js index 1d88553..9f6eb63 100644 --- a/specs/lib/imageSpec.js +++ b/specs/lib/imageSpec.js @@ -19,9 +19,9 @@ describe('image', function() { expect(loadImage(pngImage) .then(function(info) { expect(info.transparent).toBe(false); - expect(info.data).toBeDefined(); - expect(info.uri.indexOf('data:image/png') === 0).toBe(true); expect(info.format).toBe(WebGLConstants.RGB); + expect(info.source).toBeDefined(); + expect(info.extension).toBe('.png'); }), done).toResolve(); }); @@ -29,9 +29,9 @@ describe('image', function() { expect(loadImage(jpgImage) .then(function(info) { expect(info.transparent).toBe(false); - expect(info.data).toBeDefined(); - expect(info.uri.indexOf('data:image/jpeg') === 0).toBe(true); expect(info.format).toBe(WebGLConstants.RGB); + expect(info.source).toBeDefined(); + expect(info.extension).toBe('.jpg'); }), done).toResolve(); }); @@ -39,9 +39,9 @@ describe('image', function() { expect(loadImage(jpegImage) .then(function(info) { expect(info.transparent).toBe(false); - expect(info.data).toBeDefined(); - expect(info.uri.indexOf('data:image/jpeg') === 0).toBe(true); expect(info.format).toBe(WebGLConstants.RGB); + expect(info.source).toBeDefined(); + expect(info.extension).toBe('.jpeg'); }), done).toResolve(); }); @@ -49,9 +49,9 @@ describe('image', function() { expect(loadImage(gifImage) .then(function(info) { expect(info.transparent).toBe(false); - expect(info.data).toBeDefined(); - expect(info.uri.indexOf('data:image/gif') === 0).toBe(true); expect(info.format).toBe(WebGLConstants.RGB); + expect(info.source).toBeDefined(); + expect(info.extension).toBe('.gif'); }), done).toResolve(); }); @@ -59,9 +59,9 @@ describe('image', function() { expect(loadImage(grayscaleImage) .then(function(info) { expect(info.transparent).toBe(false); - expect(info.data).toBeDefined(); - expect(info.uri.indexOf('data:image/png') === 0).toBe(true); expect(info.format).toBe(WebGLConstants.ALPHA); + expect(info.source).toBeDefined(); + expect(info.extension).toBe('.png'); }), done).toResolve(); }); @@ -69,9 +69,9 @@ describe('image', function() { expect(loadImage(transparentImage) .then(function(info) { expect(info.transparent).toBe(true); - expect(info.data).toBeDefined(); - expect(info.uri.indexOf('data:image/png') === 0).toBe(true); expect(info.format).toBe(WebGLConstants.RGBA); + expect(info.source).toBeDefined(); + expect(info.extension).toBe('.png'); }), done).toResolve(); }); @@ -79,9 +79,9 @@ describe('image', function() { expect(loadImage(opaqueAlphaImage) .then(function(info) { expect(info.transparent).toBe(false); - expect(info.data).toBeDefined(); - expect(info.uri.indexOf('data:image/png') === 0).toBe(true); expect(info.format).toBe(WebGLConstants.RGBA); + expect(info.source).toBeDefined(); + expect(info.extension).toBe('.png'); }), done).toResolve(); }); From 05b1e3adbd454ae5fd7a5517ea1825c7550674b0 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 17 Mar 2017 16:05:51 -0400 Subject: [PATCH 22/69] Added flag checkTextureAlpha --- README.md | 1 + bin/obj2gltf.js | 8 +++++++- lib/convert.js | 3 ++- lib/image.js | 15 ++++++++++++--- lib/obj.js | 14 ++++++++------ specs/lib/imageSpec.js | 13 ++++++++++--- 6 files changed, 40 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index f73505c..44a0251 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ Using obj2gltf as a command-line tool: |`--cesium`|Optimize the glTF for Cesium by using the sun as a default light source.|No, default `false`| |`--ao`|Apply ambient occlusion to the converted model.|No, default `false`| |`--bypassPipeline`|Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension.|No, default `false`| +|`--checkTextureAlpha`|Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures with an alpha channel are considered to be transparent.|No, default `false`| ## Build Instructions diff --git a/bin/obj2gltf.js b/bin/obj2gltf.js index aa4e4c4..5d975e2 100644 --- a/bin/obj2gltf.js +++ b/bin/obj2gltf.js @@ -79,6 +79,11 @@ var argv = yargs describe: 'Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension.', type: 'boolean', default: false + }, + 'checkTextureAlpha': { + describe: 'Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures with an alpha channel are considered to be transparent.', + type: 'boolean', + default: false } }).parse(args); @@ -105,7 +110,8 @@ var options = { generateNormals : argv.n, ao : argv.ao, optimizeForCesium : argv.cesium, - bypassPipeline : argv.bypassPipeline + bypassPipeline : argv.bypassPipeline, + checkTextureAlpha : argv.checkTextureAlpha }; console.time('Total'); diff --git a/lib/convert.js b/lib/convert.js index c34d8e4..489654c 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -32,6 +32,7 @@ module.exports = convert; * @param {Boolean} [options.ao=false] Apply ambient occlusion to the converted model. * @param {Boolean} [options.textureCompressionOptions] Options sent to the compressTextures stage of gltf-pipeline. * @param {Boolean} [options.bypassPipeline=false] Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension. + * @param {Boolean} [options.checkTextureAlpha=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. */ function convert(objPath, gltfPath, options) { @@ -81,7 +82,7 @@ function convert(objPath, gltfPath, options) { textureCompressionOptions : textureCompressionOptions }; - return loadObj(objPath) + return loadObj(objPath, options) .then(function(objData) { return createGltf(objData); }) diff --git a/lib/image.js b/lib/image.js index 4974d9d..1ad93ba 100644 --- a/lib/image.js +++ b/lib/image.js @@ -7,6 +7,7 @@ var Promise = require('bluebird'); var fsReadFile = Promise.promisify(fs.readFile); +var defaultValue = Cesium.defaultValue; var WebGLConstants = Cesium.WebGLConstants; module.exports = loadImage; @@ -15,11 +16,16 @@ module.exports = loadImage; * Load an image file and get information about it. * * @param {String} imagePath Path to the image file. + * @param {Object} [options] An object with the following properties: + * @param {Boolean} [options.checkTextureAlpha=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. * @returns {Promise} A promise resolving to the image information, or undefined if the file doesn't exist. * * @private */ -function loadImage(imagePath) { +function loadImage(imagePath, options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var checkTextureAlpha = defaultValue(options.checkTextureAlpha, false); + return fsReadFile(imagePath) .then(function(data) { var extension = path.extname(imagePath); @@ -38,8 +44,11 @@ function loadImage(imagePath) { info.format = getFormat(channels); if (channels === 4) { - // Need to do a finer grained check over the pixels to see if the image is actually transparent - info.transparent = isTransparent(data); + if (checkTextureAlpha) { + info.transparent = isTransparent(data); + } else { + info.transparent = true; + } } } diff --git a/lib/obj.js b/lib/obj.js index 794b9a9..e9b2159 100644 --- a/lib/obj.js +++ b/lib/obj.js @@ -51,12 +51,14 @@ var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/( * Parse an obj file. * * @param {String} objPath Path to the obj file. + * @param {Object} [options] An object with the following properties: + * @param {Boolean} [options.checkTextureAlpha=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. * @returns {Promise} A promise resolving to the obj data. * @exception {RuntimeError} The file does not have any geometry information in it. * * @private */ -function loadObj(objPath) { +function loadObj(objPath, options) { // Global store of vertex attributes listed in the obj file var positions = new ArrayStorage(ComponentDatatype.FLOAT); var normals = new ArrayStorage(ComponentDatatype.FLOAT); @@ -273,11 +275,11 @@ function loadObj(objPath) { uvs = undefined; // Load materials and images - return finishLoading(nodes, mtlPaths, objPath); + return finishLoading(nodes, mtlPaths, objPath, options); }); } -function finishLoading(nodes, mtlPaths, objPath) { +function finishLoading(nodes, mtlPaths, objPath, options) { nodes = cleanNodes(nodes); if (nodes.length === 0) { throw new RuntimeError(objPath + ' does not have any geometry data'); @@ -285,7 +287,7 @@ function finishLoading(nodes, mtlPaths, objPath) { return loadMaterials(mtlPaths, objPath) .then(function(materials) { var imagePaths = getImagePaths(materials); - return loadImages(imagePaths, objPath) + return loadImages(imagePaths, options) .then(function(images) { return { nodes : nodes, @@ -316,10 +318,10 @@ function loadMaterials(mtlPaths, objPath) { }); } -function loadImages(imagePaths) { +function loadImages(imagePaths, options) { var images = {}; return Promise.map(imagePaths, function(imagePath) { - return loadImage(imagePath) + return loadImage(imagePath, options) .then(function(image) { if (defined(image)) { images[imagePath] = image; diff --git a/specs/lib/imageSpec.js b/specs/lib/imageSpec.js index 9f6eb63..c046463 100644 --- a/specs/lib/imageSpec.js +++ b/specs/lib/imageSpec.js @@ -77,11 +77,18 @@ describe('image', function() { it('loads image with fully opaque alpha channel', function(done) { expect(loadImage(opaqueAlphaImage) + .then(function(info) { + expect(info.transparent).toBe(true); + }), done).toResolve(); + }); + + it('loads image with fully opaque alpha channel with checkTextureAlpha flag', function(done) { + var options = { + checkTextureAlpha : true + }; + expect(loadImage(opaqueAlphaImage, options) .then(function(info) { expect(info.transparent).toBe(false); - expect(info.format).toBe(WebGLConstants.RGBA); - expect(info.source).toBeDefined(); - expect(info.extension).toBe('.png'); }), done).toResolve(); }); From 4583b38ebc5b1ec5616ff8c9ab3bc29565897d39 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 17 Mar 2017 16:13:08 -0400 Subject: [PATCH 23/69] Updated LICENSE.md --- LICENSE.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/LICENSE.md b/LICENSE.md index 2a7cb66..9ca92f4 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -110,6 +110,30 @@ https://www.npmjs.com/package/gltf-pipeline > > Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +### mime + +https://www.npmjs.com/package/mime + +> Copyright (c) 2010 Benjamin Thomas, Robert Kieffer +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + ### pngjs https://www.npmjs.com/package/pngjs From e48320572b4b4b3509fcef7404fb8b0ebeba72d6 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 21 Mar 2017 11:29:02 -0400 Subject: [PATCH 24/69] Fix base path since separate resources are saved in the output folder --- lib/convert.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/convert.js b/lib/convert.js index 489654c..23f48e0 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -56,7 +56,7 @@ function convert(objPath, gltfPath, options) { throw new DeveloperError('gltfPath is required'); } - var basePath = path.dirname(objPath); + var basePath = path.dirname(gltfPath); var modelName = path.basename(objPath, path.extname(objPath)); var extension = path.extname(gltfPath); if (extension === '.glb') { From 83e7723b1c55122352e9e455952391c3353fbf7e Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 21 Mar 2017 14:37:52 -0400 Subject: [PATCH 25/69] Transparency and other updates --- README.md | 2 +- bin/obj2gltf.js | 4 ++-- lib/convert.js | 6 +++++- lib/image.js | 46 +++++++++++++++++++++++++----------------- lib/obj.js | 2 +- lib/writeUris.js | 3 +-- specs/lib/gltfSpec.js | 36 ++++++++++++++++++--------------- specs/lib/imageSpec.js | 4 ++-- 8 files changed, 60 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 44a0251..a1b625a 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Using obj2gltf as a command-line tool: |`--cesium`|Optimize the glTF for Cesium by using the sun as a default light source.|No, default `false`| |`--ao`|Apply ambient occlusion to the converted model.|No, default `false`| |`--bypassPipeline`|Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension.|No, default `false`| -|`--checkTextureAlpha`|Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures with an alpha channel are considered to be transparent.|No, default `false`| +|`--hasTransparency`|Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures with an alpha channel are considered to be transparent.|No, default `false`| ## Build Instructions diff --git a/bin/obj2gltf.js b/bin/obj2gltf.js index 5d975e2..0b7bd66 100644 --- a/bin/obj2gltf.js +++ b/bin/obj2gltf.js @@ -80,7 +80,7 @@ var argv = yargs type: 'boolean', default: false }, - 'checkTextureAlpha': { + 'hasTransparency': { describe: 'Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures with an alpha channel are considered to be transparent.', type: 'boolean', default: false @@ -111,7 +111,7 @@ var options = { ao : argv.ao, optimizeForCesium : argv.cesium, bypassPipeline : argv.bypassPipeline, - checkTextureAlpha : argv.checkTextureAlpha + hasTransparency : argv.hasTransparency }; console.time('Total'); diff --git a/lib/convert.js b/lib/convert.js index 23f48e0..aef3037 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -32,7 +32,7 @@ module.exports = convert; * @param {Boolean} [options.ao=false] Apply ambient occlusion to the converted model. * @param {Boolean} [options.textureCompressionOptions] Options sent to the compressTextures stage of gltf-pipeline. * @param {Boolean} [options.bypassPipeline=false] Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension. - * @param {Boolean} [options.checkTextureAlpha=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. + * @param {Boolean} [options.hasTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. */ function convert(objPath, gltfPath, options) { @@ -61,6 +61,10 @@ function convert(objPath, gltfPath, options) { var extension = path.extname(gltfPath); if (extension === '.glb') { binary = true; + if (bypassPipeline) { + console.log('--bypassPipeline does not convert to binary glTF, saving as .gltf'); + extension = '.gltf'; + } } gltfPath = path.join(path.dirname(gltfPath), modelName + extension); diff --git a/lib/image.js b/lib/image.js index 1ad93ba..568c663 100644 --- a/lib/image.js +++ b/lib/image.js @@ -1,11 +1,11 @@ 'use strict'; var Cesium = require('cesium'); -var fs = require('fs-extra'); +var fsExtra = require('fs-extra'); var path = require('path'); var PNG = require('pngjs').PNG; var Promise = require('bluebird'); -var fsReadFile = Promise.promisify(fs.readFile); +var fsExtraReadFile = Promise.promisify(fsExtra.readFile); var defaultValue = Cesium.defaultValue; var WebGLConstants = Cesium.WebGLConstants; @@ -17,16 +17,16 @@ module.exports = loadImage; * * @param {String} imagePath Path to the image file. * @param {Object} [options] An object with the following properties: - * @param {Boolean} [options.checkTextureAlpha=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. + * @param {Boolean} [options.hasTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. * @returns {Promise} A promise resolving to the image information, or undefined if the file doesn't exist. * * @private */ function loadImage(imagePath, options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); - var checkTextureAlpha = defaultValue(options.checkTextureAlpha, false); + var hasTransparency = defaultValue(options.hasTransparency, false); - return fsReadFile(imagePath) + return fsExtraReadFile(imagePath) .then(function(data) { var extension = path.extname(imagePath); @@ -44,10 +44,13 @@ function loadImage(imagePath, options) { info.format = getFormat(channels); if (channels === 4) { - if (checkTextureAlpha) { - info.transparent = isTransparent(data); - } else { - info.transparent = true; + info.transparent = true; + if (hasTransparency) { + return isTransparent(data) + .then(function(transparent) { + info.transparent = transparent; + return info; + }); } } } @@ -61,15 +64,22 @@ function loadImage(imagePath, options) { } function isTransparent(data) { - var decoded = PNG.sync.read(data); - var pixels = decoded.data; - var pixelsLength = decoded.width * decoded.height; - for (var i = 0; i < pixelsLength; ++i) { - if (pixels[i * 4 + 3] < 255) { - return true; - } - } - return false; + return new Promise(function(resolve, reject) { + new PNG().parse(data, function(error, data) { + if (error) { + reject(error); + } + var pixels = data.data; + var pixelsLength = data.width * data.height; + for (var i = 0; i < pixelsLength; ++i) { + if (pixels[i * 4 + 3] < 255) { + resolve(true); + return; + } + } + resolve(false); + }); + }); } function getChannels(colorType) { diff --git a/lib/obj.js b/lib/obj.js index e9b2159..af6be9d 100644 --- a/lib/obj.js +++ b/lib/obj.js @@ -52,7 +52,7 @@ var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/( * * @param {String} objPath Path to the obj file. * @param {Object} [options] An object with the following properties: - * @param {Boolean} [options.checkTextureAlpha=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. + * @param {Boolean} [options.hasTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. * @returns {Promise} A promise resolving to the obj data. * @exception {RuntimeError} The file does not have any geometry information in it. * diff --git a/lib/writeUris.js b/lib/writeUris.js index 286ed72..f49c759 100644 --- a/lib/writeUris.js +++ b/lib/writeUris.js @@ -52,10 +52,9 @@ function writeUris(gltf, gltfPath, separateBuffers, separateTextures) { writeEmbeddedTextures(gltf); } - deleteExtras(gltf); - return Promise.all(promises) .then(function() { + deleteExtras(gltf); return gltf; }); } diff --git a/specs/lib/gltfSpec.js b/specs/lib/gltfSpec.js index 260a116..297918e 100644 --- a/specs/lib/gltfSpec.js +++ b/specs/lib/gltfSpec.js @@ -57,30 +57,34 @@ describe('gltf', function() { ]).then(done); }); - it('simple gltf', function() { + it('simple gltf', function(done) { var objData = clone(boxObjData, true); var gltf = createGltf(objData); - writeUris(gltf, boxGltfUrl, false, false); - expect(gltf).toEqual(boxGltf); + expect(writeUris(gltf, boxGltfUrl, false, false) + .then(function() { + expect(gltf).toEqual(boxGltf); + }), done).toResolve(); }); - it('multiple nodes, meshes, and primitives', function() { + it('multiple nodes, meshes, and primitives', function(done) { var objData = clone(groupObjData, true); var gltf = createGltf(objData); - writeUris(gltf, groupGltfUrl, false, false); - expect(gltf).toEqual(groupGltf); - expect(Object.keys(gltf.materials).length).toBe(3); - expect(Object.keys(gltf.nodes).length).toBe(1); - expect(Object.keys(gltf.meshes).length).toBe(3); + expect(writeUris(gltf, groupGltfUrl, false, false) + .then(function() { + expect(gltf).toEqual(groupGltf); + expect(Object.keys(gltf.materials).length).toBe(3); + expect(Object.keys(gltf.nodes).length).toBe(1); + expect(Object.keys(gltf.meshes).length).toBe(3); - // Check for two primitives in each mesh - for (var id in gltf.meshes) { - if (gltf.meshes.hasOwnProperty(id)) { - var mesh = gltf.meshes[id]; - expect(mesh.primitives.length).toBe(2); - } - } + // Check for two primitives in each mesh + for (var id in gltf.meshes) { + if (gltf.meshes.hasOwnProperty(id)) { + var mesh = gltf.meshes[id]; + expect(mesh.primitives.length).toBe(2); + } + } + }), done).toResolve(); }); it('sets default material values', function() { diff --git a/specs/lib/imageSpec.js b/specs/lib/imageSpec.js index c046463..9fe5277 100644 --- a/specs/lib/imageSpec.js +++ b/specs/lib/imageSpec.js @@ -82,9 +82,9 @@ describe('image', function() { }), done).toResolve(); }); - it('loads image with fully opaque alpha channel with checkTextureAlpha flag', function(done) { + it('loads image with fully opaque alpha channel with hasTransparency flag', function(done) { var options = { - checkTextureAlpha : true + hasTransparency : true }; expect(loadImage(opaqueAlphaImage, options) .then(function(info) { From e3941daf8cf2949b5fc00e5d0d6963533c941214 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 21 Mar 2017 14:51:48 -0400 Subject: [PATCH 26/69] Fix --- lib/image.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/image.js b/lib/image.js index 568c663..f596dbf 100644 --- a/lib/image.js +++ b/lib/image.js @@ -68,6 +68,7 @@ function isTransparent(data) { new PNG().parse(data, function(error, data) { if (error) { reject(error); + return; } var pixels = data.data; var pixelsLength = data.width * data.height; From be07ab0ce17b525345ef22bd2ae54cebce7943c3 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 22 Mar 2017 16:50:03 -0400 Subject: [PATCH 27/69] Added coveralls --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index e1df602..4ea0e01 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "yargs": "^7.0.1" }, "devDependencies": { + "coveralls": "^2.12.0", "gulp": "^3.9.1", "gulp-jshint": "^2.0.4", "istanbul": "^0.4.5", From 74c91320a590495d6365edeb2f978f62014ca984 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 4 Apr 2017 11:24:39 -0400 Subject: [PATCH 28/69] Travis coveralls fix --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 52ae1c4..a066d24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,6 @@ script: - npm run test -- --failTaskOnError --suppressPassed after_success: - ## We only need to run coveralls for one node version (doesn't matter which one). - ## We also ignore publishing failures, since restarting an existing travis build would otherwise break. - - if [$(node --version) == v6*]; then npm run coverage && npm run coveralls; fi +## We only need to run coveralls for one node version (doesn't matter which one). +## We also ignore publishing failures, since restarting an existing travis build would otherwise break. + - if node --version | grep -q ^v6 ; npm run coverage && npm run coveralls; fi From 04ad694599dfec999e4b0c63e3c3e54933bdec8f Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 4 Apr 2017 11:32:18 -0400 Subject: [PATCH 29/69] Trigger coverall --- lib/writeUris.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/writeUris.js b/lib/writeUris.js index f49c759..738c283 100644 --- a/lib/writeUris.js +++ b/lib/writeUris.js @@ -118,7 +118,7 @@ function writeEmbeddedTextures(gltf) { } /** - * Exposed for testing + * Exposed for testing. * * @private */ From e4b3ad54099e6147eb265daa12afce7820077e3e Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 4 Apr 2017 15:09:58 -0400 Subject: [PATCH 30/69] Style --- bin/obj2gltf.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/bin/obj2gltf.js b/bin/obj2gltf.js index 0b7bd66..56e02e5 100644 --- a/bin/obj2gltf.js +++ b/bin/obj2gltf.js @@ -17,70 +17,70 @@ var argv = yargs .help('h') .alias('h', 'help') .options({ - 'input': { + input : { alias: 'i', describe: 'Path to the obj file.', type: 'string', normalize: true }, - 'output': { + output : { alias: 'o', describe: 'Path of the converted glTF file.', type: 'string', normalize: true }, - 'binary': { + binary : { alias: 'b', describe: 'Save as binary glTF.', type: 'boolean', default: false }, - 'separate': { + separate : { alias: 's', describe: 'Write separate geometry data files, shader files, and textures instead of embedding them in the glTF.', type: 'boolean', default: false }, - 'separateTextures': { + separateTextures : { alias: 't', describe: 'Write out separate textures only.', type: 'boolean', default: false }, - 'compress': { + compress : { alias: 'c', describe: 'Quantize positions, compress texture coordinates, and oct-encode normals.', type: 'boolean', default: false }, - 'optimize': { + optimize : { alias: 'z', describe: 'Use the optimization stages in the glTF pipeline.', type: 'boolean', default: false }, - 'cesium': { + cesium : { describe: 'Optimize the glTF for Cesium by using the sun as a default light source.', type: 'boolean', default: false }, - 'generateNormals': { + generateNormals : { alias: 'n', describe: 'Generate normals if they are missing.', type: 'boolean', default: false }, - 'ao': { + ao : { describe: 'Apply ambient occlusion to the converted model.', type: 'boolean', default: false }, - 'bypassPipeline': { + bypassPipeline : { describe: 'Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension.', type: 'boolean', default: false }, - 'hasTransparency': { + hasTransparency : { describe: 'Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures with an alpha channel are considered to be transparent.', type: 'boolean', default: false From 3c958e1d86c526e12b8c98ff6e468199baeafaed Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 4 Apr 2017 16:45:21 -0400 Subject: [PATCH 31/69] Add secure checking of paths --- README.md | 1 + bin/obj2gltf.js | 8 +++++++- lib/convert.js | 1 + lib/image.js | 4 ---- lib/mtl.js | 4 ---- lib/obj.js | 33 +++++++++++++++++++++++++++++---- specs/lib/imageSpec.js | 9 --------- specs/lib/mtlSpec.js | 9 --------- specs/lib/objSpec.js | 28 ++++++++++++++++++++++++++++ 9 files changed, 66 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index a1b625a..7b7dc08 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ Using obj2gltf as a command-line tool: |`--ao`|Apply ambient occlusion to the converted model.|No, default `false`| |`--bypassPipeline`|Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension.|No, default `false`| |`--hasTransparency`|Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures with an alpha channel are considered to be transparent.|No, default `false`| +|`--secure`|Prevent the converter from reading image or mtl files outside of the input obj directory.|No, default `false`| ## Build Instructions diff --git a/bin/obj2gltf.js b/bin/obj2gltf.js index 56e02e5..654d9f8 100644 --- a/bin/obj2gltf.js +++ b/bin/obj2gltf.js @@ -84,6 +84,11 @@ var argv = yargs describe: 'Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures with an alpha channel are considered to be transparent.', type: 'boolean', default: false + }, + secure : { + describe: 'Prevent the converter from reading image or mtl files outside of the input obj directory.', + type: 'boolean', + default: false } }).parse(args); @@ -111,7 +116,8 @@ var options = { ao : argv.ao, optimizeForCesium : argv.cesium, bypassPipeline : argv.bypassPipeline, - hasTransparency : argv.hasTransparency + hasTransparency : argv.hasTransparency, + secure : argv.secure }; console.time('Total'); diff --git a/lib/convert.js b/lib/convert.js index aef3037..0014f28 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -33,6 +33,7 @@ module.exports = convert; * @param {Boolean} [options.textureCompressionOptions] Options sent to the compressTextures stage of gltf-pipeline. * @param {Boolean} [options.bypassPipeline=false] Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension. * @param {Boolean} [options.hasTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. + * @param {Boolean} [options.secure=false] Prevent the converter from reading image or mtl files outside of the input obj directory. */ function convert(objPath, gltfPath, options) { diff --git a/lib/image.js b/lib/image.js index f596dbf..e5cbc45 100644 --- a/lib/image.js +++ b/lib/image.js @@ -56,10 +56,6 @@ function loadImage(imagePath, options) { } return info; - }) - .catch(function() { - console.log('Could not read image file at ' + imagePath + '. Material will ignore this image.'); - return undefined; }); } diff --git a/lib/mtl.js b/lib/mtl.js index 50a93fe..4ced92e 100644 --- a/lib/mtl.js +++ b/lib/mtl.js @@ -101,10 +101,6 @@ function loadMtl(mtlPath) { return readLines(mtlPath, parseLine) .then(function() { return materials; - }) - .catch(function() { - console.log('Could not read material file at ' + mtlPath + '. Using default material instead.'); - return {}; }); } diff --git a/lib/obj.js b/lib/obj.js index af6be9d..455709a 100644 --- a/lib/obj.js +++ b/lib/obj.js @@ -53,12 +53,18 @@ var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/( * @param {String} objPath Path to the obj file. * @param {Object} [options] An object with the following properties: * @param {Boolean} [options.hasTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. + * @param {Boolean} [options.secure=false] Prevent the converter from reading image or mtl files outside of the input obj directory. * @returns {Promise} A promise resolving to the obj data. * @exception {RuntimeError} The file does not have any geometry information in it. * * @private */ function loadObj(objPath, options) { + options = combine(options, { + hasTransparency : false, + secure : false + }); + // Global store of vertex attributes listed in the obj file var positions = new ArrayStorage(ComponentDatatype.FLOAT); var normals = new ArrayStorage(ComponentDatatype.FLOAT); @@ -284,10 +290,10 @@ function finishLoading(nodes, mtlPaths, objPath, options) { if (nodes.length === 0) { throw new RuntimeError(objPath + ' does not have any geometry data'); } - return loadMaterials(mtlPaths, objPath) + return loadMaterials(mtlPaths, objPath, options) .then(function(materials) { var imagePaths = getImagePaths(materials); - return loadImages(imagePaths, options) + return loadImages(imagePaths, objPath, options) .then(function(images) { return { nodes : nodes, @@ -305,27 +311,46 @@ function getAbsolutePath(mtlPath, objPath) { return mtlPath; } -function loadMaterials(mtlPaths, objPath) { +function outsideDirectory(filePath, objPath) { + return (path.relative(path.dirname(objPath), filePath).indexOf('..') === 0); +} + +function loadMaterials(mtlPaths, objPath, options) { var materials = {}; return Promise.map(mtlPaths, function(mtlPath) { mtlPath = getAbsolutePath(mtlPath, objPath); + if (options.secure && outsideDirectory(mtlPath, objPath)) { + console.log('Could not read mtl file at ' + mtlPath + ' because it is outside of the obj directory and the secure flag is true. Using default material instead.'); + return; + } return loadMtl(mtlPath) .then(function(materialsInMtl) { materials = combine(materials, materialsInMtl); + }) + .catch(function() { + console.log('Could not read mtl file at ' + mtlPath + '. Using default material instead.'); }); }).then(function() { return materials; }); } -function loadImages(imagePaths, options) { +function loadImages(imagePaths, objPath, options) { var images = {}; return Promise.map(imagePaths, function(imagePath) { + if (options.secure && outsideDirectory(imagePath, objPath)) { + console.log('Could not read image file at ' + imagePath + ' because it is outside of the obj directory and the secure flag is true. Material will ignore this image.'); + return; + } return loadImage(imagePath, options) .then(function(image) { if (defined(image)) { images[imagePath] = image; } + }) + .catch(function() { + console.log('Could not read image file at ' + imagePath + '. Material will ignore this image.'); + return undefined; }); }).then(function() { return images; diff --git a/specs/lib/imageSpec.js b/specs/lib/imageSpec.js index 9fe5277..e1eff09 100644 --- a/specs/lib/imageSpec.js +++ b/specs/lib/imageSpec.js @@ -91,13 +91,4 @@ describe('image', function() { expect(info.transparent).toBe(false); }), done).toResolve(); }); - - it('handles invalid image file', function(done) { - spyOn(console, 'log'); - expect(loadImage(invalidImage) - .then(function(image) { - expect(image).toBeUndefined(); - expect(console.log.calls.argsFor(0)[0].indexOf('Could not read image file') >= 0).toBe(true); - }), done).toResolve(); - }); }); diff --git a/specs/lib/mtlSpec.js b/specs/lib/mtlSpec.js index 36be197..0be943c 100644 --- a/specs/lib/mtlSpec.js +++ b/specs/lib/mtlSpec.js @@ -41,13 +41,4 @@ describe('mtl', function() { expect(materials.Blue.diffuseColor).toEqual([0.0, 0.0, 0.64, 1.0]); }), done).toResolve(); }); - - it('handles invalid mtl file', function(done) { - spyOn(console, 'log'); - expect(loadMtl(invalidMaterialUrl) - .then(function(materials) { - expect(materials).toEqual({}); - expect(console.log.calls.argsFor(0)[0].indexOf('Could not read material file') >= 0).toBe(true); - }), done).toResolve(); - }); }); diff --git a/specs/lib/objSpec.js b/specs/lib/objSpec.js index b064bdc..c1aeac3 100644 --- a/specs/lib/objSpec.js +++ b/specs/lib/objSpec.js @@ -21,6 +21,7 @@ var objMultipleMaterialsUrl = 'specs/data/box-multiple-materials/box-multiple-ma var objUncleanedUrl = 'specs/data/box-uncleaned/box-uncleaned.obj'; var objMtllibUrl = 'specs/data/box-mtllib/box-mtllib.obj'; var objMissingMtllibUrl = 'specs/data/box-missing-mtllib/box-missing-mtllib.obj'; +var objExternalResourcesUrl = 'specs/data/box-external-resources/box-external-resources.obj'; var objTexturedUrl = 'specs/data/box-textured/box-textured.obj'; var objMissingTextureUrl = 'specs/data/box-missing-texture/box-missing-texture.obj'; var objSubdirectoriesUrl = 'specs/data/box-subdirectories/box-textured.obj'; @@ -269,6 +270,32 @@ describe('obj', function() { expect(loadObj(objMissingMtllibUrl) .then(function(data) { expect(data.materials).toEqual({}); + expect(console.log.calls.argsFor(0)[0].indexOf('Could not read mtl file') >= 0).toBe(true); + }), done).toResolve(); + }); + + it('loads resources outside of the obj directory', function(done) { + expect(loadObj(objExternalResourcesUrl) + .then(function(data) { + var imagePath = getImagePath(objTexturedUrl, 'cesium.png'); + expect(data.images[imagePath]).toBeDefined(); + expect(data.materials.MaterialTextured.diffuseColorMap).toEqual(imagePath); + }), done).toResolve(); + }); + + it('does not load resources outside of the obj directory when secure is true', function(done) { + spyOn(console, 'log'); + var options = { + secure : true + }; + expect(loadObj(objExternalResourcesUrl, options) + .then(function(data) { + var imagePath = getImagePath(objMissingTextureUrl, 'cesium.png'); + expect(data.images[imagePath]).toBeUndefined(); + expect(data.materials.MaterialTextured).toBeDefined(); + expect(data.materials.Material).toBeUndefined(); // Not in directory, so not included + expect(console.log.calls.argsFor(0)[0].indexOf('Could not read mtl file') >= 0).toBe(true); + expect(console.log.calls.argsFor(1)[0].indexOf('Could not read image file') >= 0).toBe(true); }), done).toResolve(); }); @@ -288,6 +315,7 @@ describe('obj', function() { var imagePath = getImagePath(objMissingTextureUrl, 'cesium.png'); expect(data.images[imagePath]).toBeUndefined(); expect(data.materials.Material.diffuseColorMap).toEqual(imagePath); + expect(console.log.calls.argsFor(0)[0].indexOf('Could not read image file') >= 0).toBe(true); }), done).toResolve(); }); From c7b4fc3cb1e8ea81c85b3dc190726b516eca404a Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 4 Apr 2017 17:21:10 -0400 Subject: [PATCH 32/69] Custom logger --- lib/convert.js | 22 ++++++++++++++++++---- lib/obj.js | 15 ++++++++++----- lib/writeUris.js | 5 +++-- specs/lib/convertSpec.js | 17 +++++++++++++++++ 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/lib/convert.js b/lib/convert.js index 0014f28..405c5ce 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -34,10 +34,10 @@ module.exports = convert; * @param {Boolean} [options.bypassPipeline=false] Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension. * @param {Boolean} [options.hasTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. * @param {Boolean} [options.secure=false] Prevent the converter from reading image or mtl files outside of the input obj directory. + * @param {Logger} [options.logger] A callback function for handling logged messages. Defaults to console.log. */ - function convert(objPath, gltfPath, options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); + options = defaultValue(options, {}); var binary = defaultValue(options.binary, false); var separate = defaultValue(options.separate, false); var separateTextures = defaultValue(options.separateTextures, false) || separate; @@ -48,6 +48,10 @@ function convert(objPath, gltfPath, options) { var ao = defaultValue(options.ao, false); var textureCompressionOptions = options.textureCompressionOptions; var bypassPipeline = defaultValue(options.bypassPipeline, false); + var logger = defaultValue(options.logger, defaultLogger); + options.logger = logger; + options.hasTransparency = defaultValue(options.hasTransparency, false); + options.secure = defaultValue(options.secure, false); if (!defined(objPath)) { throw new DeveloperError('objPath is required'); @@ -63,7 +67,7 @@ function convert(objPath, gltfPath, options) { if (extension === '.glb') { binary = true; if (bypassPipeline) { - console.log('--bypassPipeline does not convert to binary glTF, saving as .gltf'); + options.logger('--bypassPipeline does not convert to binary glTF, saving as .gltf'); extension = '.gltf'; } } @@ -92,7 +96,7 @@ function convert(objPath, gltfPath, options) { return createGltf(objData); }) .then(function(gltf) { - return writeUris(gltf, gltfPath, separate, separateTextures); + return writeUris(gltf, gltfPath, separate, separateTextures, logger); }) .then(function(gltf) { if (bypassPipeline) { @@ -109,3 +113,13 @@ function convert(objPath, gltfPath, options) { * @private */ convert._outputJson = fsExtraOutputJson; + +/** + * A callback function that logs messages. + * @callback Logger + * + * @param {String} message The message to log. + */ +var defaultLogger = function(message) { + console.log(message); +}; diff --git a/lib/obj.js b/lib/obj.js index 455709a..63ab31d 100644 --- a/lib/obj.js +++ b/lib/obj.js @@ -54,15 +54,20 @@ var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/( * @param {Object} [options] An object with the following properties: * @param {Boolean} [options.hasTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. * @param {Boolean} [options.secure=false] Prevent the converter from reading image or mtl files outside of the input obj directory. + * @param {Boolean} [options.logger] A callback function for handling logged messages. Defaults to console.log. * @returns {Promise} A promise resolving to the obj data. * @exception {RuntimeError} The file does not have any geometry information in it. * * @private */ function loadObj(objPath, options) { + // The defaults are set in convert as well, this just helps with testing loadObj individually options = combine(options, { hasTransparency : false, - secure : false + secure : false, + logger : function(message) { + console.log(message); + } }); // Global store of vertex attributes listed in the obj file @@ -320,7 +325,7 @@ function loadMaterials(mtlPaths, objPath, options) { return Promise.map(mtlPaths, function(mtlPath) { mtlPath = getAbsolutePath(mtlPath, objPath); if (options.secure && outsideDirectory(mtlPath, objPath)) { - console.log('Could not read mtl file at ' + mtlPath + ' because it is outside of the obj directory and the secure flag is true. Using default material instead.'); + options.logger('Could not read mtl file at ' + mtlPath + ' because it is outside of the obj directory and the secure flag is true. Using default material instead.'); return; } return loadMtl(mtlPath) @@ -328,7 +333,7 @@ function loadMaterials(mtlPaths, objPath, options) { materials = combine(materials, materialsInMtl); }) .catch(function() { - console.log('Could not read mtl file at ' + mtlPath + '. Using default material instead.'); + options.logger('Could not read mtl file at ' + mtlPath + '. Using default material instead.'); }); }).then(function() { return materials; @@ -339,7 +344,7 @@ function loadImages(imagePaths, objPath, options) { var images = {}; return Promise.map(imagePaths, function(imagePath) { if (options.secure && outsideDirectory(imagePath, objPath)) { - console.log('Could not read image file at ' + imagePath + ' because it is outside of the obj directory and the secure flag is true. Material will ignore this image.'); + options.logger('Could not read image file at ' + imagePath + ' because it is outside of the obj directory and the secure flag is true. Material will ignore this image.'); return; } return loadImage(imagePath, options) @@ -349,7 +354,7 @@ function loadImages(imagePaths, objPath, options) { } }) .catch(function() { - console.log('Could not read image file at ' + imagePath + '. Material will ignore this image.'); + options.logger('Could not read image file at ' + imagePath + '. Material will ignore this image.'); return undefined; }); }).then(function() { diff --git a/lib/writeUris.js b/lib/writeUris.js index 738c283..af88ec7 100644 --- a/lib/writeUris.js +++ b/lib/writeUris.js @@ -15,11 +15,12 @@ module.exports = writeUris; * @param {String} gltfPath Path where the glTF will be saved. * @param {Boolean} separateBuffers Writes out separate buffers. * @param {Boolean} separateTextures Writes out separate textures. + * @param {Logger} logger A callback function for handling logged messages. Defaults to console.log. * @returns {Promise} A promise that resolves to the glTF asset. * * @private */ -function writeUris(gltf, gltfPath, separateBuffers, separateTextures) { +function writeUris(gltf, gltfPath, separateBuffers, separateTextures, logger) { var promises = []; var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; @@ -37,7 +38,7 @@ function writeUris(gltf, gltfPath, separateBuffers, separateTextures) { var exceedsMaximum = (texturesByteLength + bufferByteLength > 201326580); if (exceedsMaximum) { - console.log('Buffers and textures are too large to encode in the glTF, saving as separate resources.'); + logger('Buffers and textures are too large to encode in the glTF, saving as separate resources.'); } if (separateBuffers || exceedsMaximum) { diff --git a/specs/lib/convertSpec.js b/specs/lib/convertSpec.js index a409911..e0b6c64 100644 --- a/specs/lib/convertSpec.js +++ b/specs/lib/convertSpec.js @@ -8,6 +8,8 @@ var objPath = 'specs/data/box-textured/box-textured.obj'; var gltfPath = 'specs/data/box-textured/box-textured.gltf'; var glbPath = 'specs/data/box-textured/box-textured.glb'; +var objExternalResourcesPath = 'specs/data/box-external-resources/box-external-resources.obj'; + describe('convert', function() { it('converts an obj to gltf', function(done) { var spy = spyOn(GltfPipeline, 'processJSONToDisk'); @@ -111,6 +113,21 @@ describe('convert', function() { }), done).toResolve(); }); + it('Uses a custom logger', function(done) { + var spy = spyOn(GltfPipeline, 'processJSONToDisk'); + var logCount = 0; + var options = { + secure : true, // Needs to be set to trigger messages + logger : function() { + logCount++; + } + }; + expect(convert(objExternalResourcesPath, gltfPath, options) + .then(function() { + expect(logCount).toEqual(2); + }), done).toResolve(); + }); + it('throws if objPath is undefined', function() { expect(function() { convert(undefined, gltfPath); From 02c3aa18dbc1041a44cba57f50892328915f131e Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 4 Apr 2017 17:55:00 -0400 Subject: [PATCH 33/69] Support KHR_materials_common output --- README.md | 1 + bin/obj2gltf.js | 5 +++++ lib/convert.js | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/README.md b/README.md index 7b7dc08..eae15e4 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ Using obj2gltf as a command-line tool: |`-n`|Generate normals if they are missing.|No, default `false`| |`--cesium`|Optimize the glTF for Cesium by using the sun as a default light source.|No, default `false`| |`--ao`|Apply ambient occlusion to the converted model.|No, default `false`| +|`--kmc|Output glTF with the KHR_materials_common extension.|No, default `false`| |`--bypassPipeline`|Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension.|No, default `false`| |`--hasTransparency`|Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures with an alpha channel are considered to be transparent.|No, default `false`| |`--secure`|Prevent the converter from reading image or mtl files outside of the input obj directory.|No, default `false`| diff --git a/bin/obj2gltf.js b/bin/obj2gltf.js index 654d9f8..b1657a3 100644 --- a/bin/obj2gltf.js +++ b/bin/obj2gltf.js @@ -75,6 +75,11 @@ var argv = yargs type: 'boolean', default: false }, + kmc : { + describe: 'Output glTF with the KHR_materials_common extension.', + type: 'boolean', + default: false + }, bypassPipeline : { describe: 'Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension.', type: 'boolean', diff --git a/lib/convert.js b/lib/convert.js index 405c5ce..e8ce59b 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -30,6 +30,7 @@ module.exports = convert; * @param {Boolean} [options.optimizeForCesium=false] Optimize the glTF for Cesium by using the sun as a default light source. * @param {Boolean} [options.generateNormals=false] Generate normals if they are missing. * @param {Boolean} [options.ao=false] Apply ambient occlusion to the converted model. + * @param {Boolean} [options.kmc=false] Output glTF with the KHR_materials_common extension. * @param {Boolean} [options.textureCompressionOptions] Options sent to the compressTextures stage of gltf-pipeline. * @param {Boolean} [options.bypassPipeline=false] Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension. * @param {Boolean} [options.hasTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. @@ -46,6 +47,7 @@ function convert(objPath, gltfPath, options) { var optimizeForCesium = defaultValue(options.optimizeForCesium, false); var generateNormals = defaultValue(options.generateNormals, false); var ao = defaultValue(options.ao, false); + var kmc = defaultValue(options.kmc, false); var textureCompressionOptions = options.textureCompressionOptions; var bypassPipeline = defaultValue(options.bypassPipeline, false); var logger = defaultValue(options.logger, defaultLogger); @@ -75,6 +77,9 @@ function convert(objPath, gltfPath, options) { var aoOptions = ao ? {} : undefined; + // TODO: gltf-pipeline uses the same kmc options for each material and doesn't recognize the transparent flag + var kmcOptions = kmc ? {} : undefined; + var pipelineOptions = { createDirectory : false, basePath : basePath, @@ -88,6 +93,7 @@ function convert(objPath, gltfPath, options) { optimizeForCesium : optimizeForCesium, smoothNormals : generateNormals, aoOptions : aoOptions, + kmcOptions : kmcOptions, textureCompressionOptions : textureCompressionOptions }; From bedbb6ef96e2e1036e3bbd6a90656f31eb3f4304 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 4 Apr 2017 17:57:01 -0400 Subject: [PATCH 34/69] Fix tests and jshint --- lib/convert.js | 20 ++++++++++---------- specs/lib/convertSpec.js | 3 +++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/convert.js b/lib/convert.js index e8ce59b..e8a6180 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -16,6 +16,16 @@ var DeveloperError = Cesium.DeveloperError; module.exports = convert; +/** + * A callback function that logs messages. + * @callback Logger + * + * @param {String} message The message to log. + */ +var defaultLogger = function(message) { + console.log(message); +}; + /** * Converts an obj file to a glTF file. * @@ -119,13 +129,3 @@ function convert(objPath, gltfPath, options) { * @private */ convert._outputJson = fsExtraOutputJson; - -/** - * A callback function that logs messages. - * @callback Logger - * - * @param {String} message The message to log. - */ -var defaultLogger = function(message) { - console.log(message); -}; diff --git a/specs/lib/convertSpec.js b/specs/lib/convertSpec.js index e0b6c64..7b4fe0e 100644 --- a/specs/lib/convertSpec.js +++ b/specs/lib/convertSpec.js @@ -40,6 +40,7 @@ describe('convert', function() { quantize : false, compressTextureCoordinates : false, aoOptions : undefined, + kmcOptions : undefined, smoothNormals : false, optimizeForCesium : false, textureCompressionOptions : undefined, @@ -63,6 +64,7 @@ describe('convert', function() { optimize : true, generateNormals : true, ao : true, + kmc : true, optimizeForCesium : true, textureCompressionOptions : textureCompressionOptions }; @@ -81,6 +83,7 @@ describe('convert', function() { quantize : true, compressTextureCoordinates : true, aoOptions : {}, + kmcOptions : {}, smoothNormals : true, optimizeForCesium : true, textureCompressionOptions : textureCompressionOptions, From 72c20eb6ee3c565df94a2a18e63013a2d5bfffb5 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 5 Apr 2017 09:13:28 -0400 Subject: [PATCH 35/69] Fix output file name --- lib/convert.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/convert.js b/lib/convert.js index e8a6180..68a83b5 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -74,7 +74,7 @@ function convert(objPath, gltfPath, options) { } var basePath = path.dirname(gltfPath); - var modelName = path.basename(objPath, path.extname(objPath)); + var modelName = path.basename(gltfPath, path.extname(gltfPath)); var extension = path.extname(gltfPath); if (extension === '.glb') { binary = true; From 965402c53540170da308079ce5d10b3d03507fec Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 5 Apr 2017 10:44:28 -0400 Subject: [PATCH 36/69] Handle file path errors and better promise handling --- bin/obj2gltf.js | 3 + lib/convert.js | 143 +++++++++++++++++++++------------------ specs/lib/convertSpec.js | 33 ++++++--- 3 files changed, 105 insertions(+), 74 deletions(-) diff --git a/bin/obj2gltf.js b/bin/obj2gltf.js index b1657a3..a10ff30 100644 --- a/bin/obj2gltf.js +++ b/bin/obj2gltf.js @@ -130,4 +130,7 @@ console.time('Total'); convert(objPath, gltfPath, options) .then(function() { console.timeEnd('Total'); + }) + .catch(function(error) { + console.log(error.message); }); diff --git a/lib/convert.js b/lib/convert.js index 68a83b5..c309067 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -48,79 +48,92 @@ var defaultLogger = function(message) { * @param {Logger} [options.logger] A callback function for handling logged messages. Defaults to console.log. */ function convert(objPath, gltfPath, options) { - options = defaultValue(options, {}); - var binary = defaultValue(options.binary, false); - var separate = defaultValue(options.separate, false); - var separateTextures = defaultValue(options.separateTextures, false) || separate; - var compress = defaultValue(options.compress, false); - var optimize = defaultValue(options.optimize, false); - var optimizeForCesium = defaultValue(options.optimizeForCesium, false); - var generateNormals = defaultValue(options.generateNormals, false); - var ao = defaultValue(options.ao, false); - var kmc = defaultValue(options.kmc, false); - var textureCompressionOptions = options.textureCompressionOptions; - var bypassPipeline = defaultValue(options.bypassPipeline, false); - var logger = defaultValue(options.logger, defaultLogger); - options.logger = logger; - options.hasTransparency = defaultValue(options.hasTransparency, false); - options.secure = defaultValue(options.secure, false); + return new Promise(function(resolve, reject) { + options = defaultValue(options, {}); + var binary = defaultValue(options.binary, false); + var separate = defaultValue(options.separate, false); + var separateTextures = defaultValue(options.separateTextures, false) || separate; + var compress = defaultValue(options.compress, false); + var optimize = defaultValue(options.optimize, false); + var optimizeForCesium = defaultValue(options.optimizeForCesium, false); + var generateNormals = defaultValue(options.generateNormals, false); + var ao = defaultValue(options.ao, false); + var kmc = defaultValue(options.kmc, false); + var textureCompressionOptions = options.textureCompressionOptions; + var bypassPipeline = defaultValue(options.bypassPipeline, false); + var logger = defaultValue(options.logger, defaultLogger); + options.logger = logger; + options.hasTransparency = defaultValue(options.hasTransparency, false); + options.secure = defaultValue(options.secure, false); - if (!defined(objPath)) { - throw new DeveloperError('objPath is required'); - } - - if (!defined(gltfPath)) { - throw new DeveloperError('gltfPath is required'); - } - - var basePath = path.dirname(gltfPath); - var modelName = path.basename(gltfPath, path.extname(gltfPath)); - var extension = path.extname(gltfPath); - if (extension === '.glb') { - binary = true; - if (bypassPipeline) { - options.logger('--bypassPipeline does not convert to binary glTF, saving as .gltf'); - extension = '.gltf'; + if (!defined(objPath)) { + throw new DeveloperError('objPath is required'); } - } - gltfPath = path.join(path.dirname(gltfPath), modelName + extension); - var aoOptions = ao ? {} : undefined; + if (!defined(gltfPath)) { + throw new DeveloperError('gltfPath is required'); + } - // TODO: gltf-pipeline uses the same kmc options for each material and doesn't recognize the transparent flag - var kmcOptions = kmc ? {} : undefined; + var objExtension = path.extname(objPath).toLowerCase(); + if (objExtension !== '.obj') { + throw new DeveloperError('Invalid obj path "' + objPath + '"'); + } - var pipelineOptions = { - createDirectory : false, - basePath : basePath, - binary : binary, - embed : !separate, - embedImage : !separateTextures, - quantize : compress, - compressTextureCoordinates : compress, - encodeNormals : compress, - preserve : !optimize, - optimizeForCesium : optimizeForCesium, - smoothNormals : generateNormals, - aoOptions : aoOptions, - kmcOptions : kmcOptions, - textureCompressionOptions : textureCompressionOptions - }; + var extension = path.extname(gltfPath).toLowerCase(); + if (extension !== '.gltf' && extension !== '.glb') { + throw new DeveloperError('Invalid gltf path "' + gltfPath + '"'); + } - return loadObj(objPath, options) - .then(function(objData) { - return createGltf(objData); - }) - .then(function(gltf) { - return writeUris(gltf, gltfPath, separate, separateTextures, logger); - }) - .then(function(gltf) { + var basePath = path.dirname(gltfPath); + var modelName = path.basename(gltfPath, path.extname(gltfPath)); + if (extension === '.glb') { + binary = true; if (bypassPipeline) { - return convert._outputJson(gltfPath, gltf); - } else { - return GltfPipeline.processJSONToDisk(gltf, gltfPath, pipelineOptions); + logger('--bypassPipeline does not convert to binary glTF, saving as .gltf'); + extension = '.gltf'; } - }); + } + gltfPath = path.join(path.dirname(gltfPath), modelName + extension); + + var aoOptions = ao ? {} : undefined; + + // TODO: gltf-pipeline uses the same kmc options for each material and doesn't recognize the transparent flag + var kmcOptions = kmc ? {} : undefined; + + var pipelineOptions = { + createDirectory : false, + basePath : basePath, + binary : binary, + embed : !separate, + embedImage : !separateTextures, + quantize : compress, + compressTextureCoordinates : compress, + encodeNormals : compress, + preserve : !optimize, + optimizeForCesium : optimizeForCesium, + smoothNormals : generateNormals, + aoOptions : aoOptions, + kmcOptions : kmcOptions, + textureCompressionOptions : textureCompressionOptions + }; + + return loadObj(objPath, options) + .then(function(objData) { + return createGltf(objData); + }) + .then(function(gltf) { + return writeUris(gltf, gltfPath, separate, separateTextures, logger); + }) + .then(function(gltf) { + if (bypassPipeline) { + return convert._outputJson(gltfPath, gltf); + } else { + return GltfPipeline.processJSONToDisk(gltf, gltfPath, pipelineOptions); + } + }) + .then(resolve) + .catch(reject); + }); } /** diff --git a/specs/lib/convertSpec.js b/specs/lib/convertSpec.js index 7b4fe0e..35fd0eb 100644 --- a/specs/lib/convertSpec.js +++ b/specs/lib/convertSpec.js @@ -1,12 +1,19 @@ 'use strict'; +var Cesium = require('cesium'); var GltfPipeline = require('gltf-pipeline').Pipeline; var path = require('path'); var convert = require('../../lib/convert'); var writeUris = require('../../lib/writeUris'); +var DeveloperError = Cesium.DeveloperError; + var objPath = 'specs/data/box-textured/box-textured.obj'; var gltfPath = 'specs/data/box-textured/box-textured.gltf'; var glbPath = 'specs/data/box-textured/box-textured.glb'; +var objPathInvalid = 'invalid/'; +var gltfPathInvalid = 'invalid/model.invalid'; +var objPathNonExistent = 'specs/data/non-existent.obj'; +var gltfPathNonExistent = 'specs/data/non-existent.gltf'; var objExternalResourcesPath = 'specs/data/box-external-resources/box-external-resources.obj'; @@ -116,7 +123,7 @@ describe('convert', function() { }), done).toResolve(); }); - it('Uses a custom logger', function(done) { + it('uses a custom logger', function(done) { var spy = spyOn(GltfPipeline, 'processJSONToDisk'); var logCount = 0; var options = { @@ -131,15 +138,23 @@ describe('convert', function() { }), done).toResolve(); }); - it('throws if objPath is undefined', function() { - expect(function() { - convert(undefined, gltfPath); - }).toThrowDeveloperError(); + it('rejects if objPath is undefined', function(done) { + expect(convert(undefined, gltfPath), done).toRejectWith(DeveloperError); }); - it('throws if gltfPath is undefined', function() { - expect(function() { - convert(objPath, undefined); - }).toThrowDeveloperError(); + it('rejects if gltfPath is undefined', function(done) { + expect(convert(objPath, undefined), done).toRejectWith(DeveloperError); + }); + + it('rejects if obj path is invalid', function(done) { + expect(convert(objPathInvalid, gltfPath), done).toRejectWith(DeveloperError); + }); + + it('rejects if gltf path is invalid', function(done) { + expect(convert(objPath, gltfPathInvalid), done).toRejectWith(DeveloperError); + }); + + it('rejects if obj path does not exist', function(done) { + expect(convert(objPathNonExistent, gltfPath), done).toRejectWith(Error); }); }); From e93e7106b7b44e3f75118cc8c771259ec9b92738 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 5 Apr 2017 10:49:31 -0400 Subject: [PATCH 37/69] Add missing test files --- .../box-external-resources.mtl | 13 ++++++ .../box-external-resources.obj | 46 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 specs/data/box-external-resources/box-external-resources.mtl create mode 100644 specs/data/box-external-resources/box-external-resources.obj diff --git a/specs/data/box-external-resources/box-external-resources.mtl b/specs/data/box-external-resources/box-external-resources.mtl new file mode 100644 index 0000000..2e2d3a5 --- /dev/null +++ b/specs/data/box-external-resources/box-external-resources.mtl @@ -0,0 +1,13 @@ +# Blender MTL File: 'box.blend' +# Material Count: 1 + +newmtl MaterialTextured +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 +map_Kd ../box-textured/cesium.png diff --git a/specs/data/box-external-resources/box-external-resources.obj b/specs/data/box-external-resources/box-external-resources.obj new file mode 100644 index 0000000..8786fa7 --- /dev/null +++ b/specs/data/box-external-resources/box-external-resources.obj @@ -0,0 +1,46 @@ +# Blender v2.78 (sub 0) OBJ File: 'box-multiple-materials.blend' +# www.blender.org +mtllib box-external-resources.mtl +mtllib ../box/box.mtl +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn -1.0000 0.0000 0.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 0.0000 0.0000 1.0000 +usemtl MaterialTextured +f 3/1/1 7/2/1 5/3/1 1/4/1 +f 1/9/3 2/10/3 4/11/3 3/12/3 +f 3/1/5 4/6/5 8/17/5 7/18/5 +usemtl Material +f 8/5/2 4/6/2 2/7/2 6/8/2 +f 7/13/4 8/14/4 6/15/4 5/16/4 +f 5/19/6 6/20/6 2/7/6 1/4/6 From 28d081e0ae2fbcf33f9b952b435f72b76db6fcf9 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 5 Apr 2017 13:17:31 -0400 Subject: [PATCH 38/69] Remove TODO comment, moved to isse --- lib/convert.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/convert.js b/lib/convert.js index c309067..9303699 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -96,8 +96,6 @@ function convert(objPath, gltfPath, options) { gltfPath = path.join(path.dirname(gltfPath), modelName + extension); var aoOptions = ao ? {} : undefined; - - // TODO: gltf-pipeline uses the same kmc options for each material and doesn't recognize the transparent flag var kmcOptions = kmc ? {} : undefined; var pipelineOptions = { From cc8fee19c407e4b5a0fd43f1930e6c55a65b5c89 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 10 Apr 2017 17:57:56 -0400 Subject: [PATCH 39/69] Many updates --- README.md | 31 ++--- bin/obj2gltf.js | 65 +++++----- index.js | 4 +- lib/Material.js | 19 +++ lib/clone.js | 54 -------- lib/convert.js | 257 +++++++++++++++++++++++++-------------- lib/gltf.js | 19 ++- lib/image.js | 47 +++---- lib/mtl.js | 41 ++----- lib/obj.js | 109 ++++++----------- lib/writeUris.js | 44 +++---- specs/lib/convertSpec.js | 73 ++++------- specs/lib/gltfSpec.js | 183 ++++++++++++++-------------- specs/lib/imageSpec.js | 53 ++++---- specs/lib/mtlSpec.js | 13 +- specs/lib/objSpec.js | 67 +++++----- 16 files changed, 515 insertions(+), 564 deletions(-) create mode 100644 lib/Material.js delete mode 100644 lib/clone.js diff --git a/README.md b/README.md index eae15e4..0773c26 100644 --- a/README.md +++ b/README.md @@ -11,45 +11,40 @@ npm install --save obj2gltf Using obj2gltf as a library: ```javascript var obj2gltf = require('obj2gltf'); -var convert = obj2gltf.convert; var options = { separateTextures : true // Don't embed textures in the converted glTF } -convert('model.obj', 'model.gltf', options) +obj2gltf('model.obj', 'model.gltf', options) .then(function() { console.log('Converted model'); }); ``` Using obj2gltf as a command-line tool: -`node bin/obj2gltf.js model.obj` - -`node bin/obj2gltf.js model.obj model.gltf` +`node bin/obj2gltf.js -i model.obj` `node bin/obj2gltf.js -i model.obj -o model.gltf` -`node bin/obj2gltf.js -i model.obj -o model.gltf -s` - ## Usage ###Command line flags: |Flag|Description|Required| |----|-----------|--------| -|`-h`|Display help.|No| -|`-i`|Path to the obj file.| :white_check_mark: Yes| -|`-o`|Path of the converted glTF file.|No| -|`-b`|Save as binary glTF.|No, default `false`| -|`-s`|Writes out separate geometry data files, shader files, and textures instead of embedding them in the glTF file.|No, default `false`| -|`-t`|Write out separate textures only.|No, default `false`| -|`-c`|Quantize positions, compress texture coordinates, and oct-encode normals.|No, default `false`| -|`-z`|Use the optimization stages in the glTF pipeline.|No, default `false`| -|`-n`|Generate normals if they are missing.|No, default `false`| -|`--cesium`|Optimize the glTF for Cesium by using the sun as a default light source.|No, default `false`| +|`-h`, `--help`|Display help.|No| +|`-i`, `--input`|Path to the obj file.| :white_check_mark: Yes| +|`-o`, `--output`|Path of the converted glTF file.|No| +|`-b`, `--binary`|Save as binary glTF.|No, default `false`| +|`-s`, `--separate`|Writes out separate geometry data files, shader files, and textures instead of embedding them in the glTF file.|No, default `false`| +|`-t`, `--separateTextures`|Write out separate textures only.|No, default `false`| +|`-c`, `--compress`|Quantize positions, compress texture coordinates, and oct-encode normals.|No, default `false`| +|`-z`, `--optimize`|Use the optimization stages in the glTF pipeline.|No, default `false`| +|`-n`, `--generateNormals`|Generate normals if they are missing.|No, default `false`| +|`--optimizeForCesium`|Optimize the glTF for Cesium by using the sun as a default light source.|No, default `false`| |`--ao`|Apply ambient occlusion to the converted model.|No, default `false`| |`--kmc|Output glTF with the KHR_materials_common extension.|No, default `false`| |`--bypassPipeline`|Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension.|No, default `false`| -|`--hasTransparency`|Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures with an alpha channel are considered to be transparent.|No, default `false`| +|`--checkTransparency`|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.|No, default `false`| |`--secure`|Prevent the converter from reading image or mtl files outside of the input obj directory.|No, default `false`| ## Build Instructions diff --git a/bin/obj2gltf.js b/bin/obj2gltf.js index a10ff30..52e4c0e 100644 --- a/bin/obj2gltf.js +++ b/bin/obj2gltf.js @@ -5,11 +5,11 @@ var path = require('path'); var yargs = require('yargs'); var convert = require('../lib/convert'); -var defaultValue = Cesium.defaultValue; var defined = Cesium.defined; +var defaults = convert.defaults; + var args = process.argv; -args = args.slice(2, args.length); var argv = yargs .usage('Usage: node $0 -i inputPath -o outputPath') @@ -21,7 +21,8 @@ var argv = yargs alias: 'i', describe: 'Path to the obj file.', type: 'string', - normalize: true + normalize: true, + demandOption: true }, output : { alias: 'o', @@ -33,77 +34,72 @@ var argv = yargs alias: 'b', describe: 'Save as binary glTF.', type: 'boolean', - default: false + default: defaults.binary }, separate : { alias: 's', describe: 'Write separate geometry data files, shader files, and textures instead of embedding them in the glTF.', type: 'boolean', - default: false + default: defaults.separate }, separateTextures : { alias: 't', describe: 'Write out separate textures only.', type: 'boolean', - default: false + default: defaults.separateTextures }, compress : { alias: 'c', describe: 'Quantize positions, compress texture coordinates, and oct-encode normals.', type: 'boolean', - default: false + default: defaults.compress }, optimize : { alias: 'z', - describe: 'Use the optimization stages in the glTF pipeline.', + describe: 'Optimize the glTF for size and runtime performance.', type: 'boolean', - default: false + default: defaults.optimize }, - cesium : { + optimizeForCesium : { describe: 'Optimize the glTF for Cesium by using the sun as a default light source.', type: 'boolean', - default: false + default: defaults.optimizeForCesium }, generateNormals : { alias: 'n', describe: 'Generate normals if they are missing.', type: 'boolean', - default: false + default: defaults.generateNormals }, ao : { describe: 'Apply ambient occlusion to the converted model.', type: 'boolean', - default: false + default: defaults.ao }, kmc : { describe: 'Output glTF with the KHR_materials_common extension.', type: 'boolean', - default: false + default: defaults.kmc }, bypassPipeline : { describe: 'Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension.', type: 'boolean', - default: false + default: defaults.bypassPipeline }, - hasTransparency : { - describe: 'Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures with an alpha channel are considered to be transparent.', + 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.', type: 'boolean', - default: false + default: defaults.checkTransparency }, secure : { describe: 'Prevent the converter from reading image or mtl files outside of the input obj directory.', type: 'boolean', - default: false + default: defaults.secure } }).parse(args); -var objPath = defaultValue(argv.i, argv._[0]); -var gltfPath = defaultValue(argv.o, argv._[1]); - -if (!defined(objPath)) { - yargs.showHelp(); - return; -} +var objPath = argv.i; +var gltfPath = argv.o; if (!defined(gltfPath)) { var extension = argv.b ? '.glb' : '.gltf'; @@ -112,16 +108,17 @@ if (!defined(gltfPath)) { } var options = { - binary : argv.b, - separate : argv.s, - separateTextures : argv.t, - compress : argv.c, - optimize : argv.z, - generateNormals : argv.n, + binary : argv.binary, + separate : argv.separate, + separateTextures : argv.separateTextures, + compress : argv.compress, + optimize : argv.optimize, + optimizeForCesium : argv.optimizeForCesium, + generateNormals : argv.generateNormals, ao : argv.ao, - optimizeForCesium : argv.cesium, + kmc : argv.kmc, bypassPipeline : argv.bypassPipeline, - hasTransparency : argv.hasTransparency, + checkTransparency : argv.checkTransparency, secure : argv.secure }; diff --git a/index.js b/index.js index 326e68e..02ae37e 100644 --- a/index.js +++ b/index.js @@ -1,3 +1 @@ -module.exports = { - convert : require('./lib/convert') -}; +module.exports = require('./lib/convert'); diff --git a/lib/Material.js b/lib/Material.js new file mode 100644 index 0000000..60d284d --- /dev/null +++ b/lib/Material.js @@ -0,0 +1,19 @@ +'use strict'; + +module.exports = Material; + +function Material() { + this.ambientColor = [0.0, 0.0, 0.0, 1.0]; // Ka + this.emissionColor = [0.0, 0.0, 0.0, 1.0]; // Ke + this.diffuseColor = [0.5, 0.5, 0.5, 1.0]; // Kd + this.specularColor = [0.0, 0.0, 0.0, 1.0]; // Ks + this.specularShininess = 0.0; // Ns + this.alpha = 1.0; // d / Tr + this.ambientTexture = undefined; // map_Ka + this.emissionTexture = undefined; // map_Ke + this.diffuseTexture = undefined; // map_Kd + this.specularTexture = undefined; // map_Ks + this.specularShininessMap = undefined; // map_Ns + this.normalMap = undefined; // map_Bump + this.alphaMap = undefined; // map_d +} diff --git a/lib/clone.js b/lib/clone.js deleted file mode 100644 index d93e059..0000000 --- a/lib/clone.js +++ /dev/null @@ -1,54 +0,0 @@ -'use strict'; -var Cesium = require('cesium'); -var ArrayStorage = require('./ArrayStorage'); - -var defaultValue = Cesium.defaultValue; - -module.exports = clone; - -/** - * Clones an object, returning a new object containing the same properties. - * Modified from Cesium.clone to support typed arrays, buffers, and the ArrayStorage class. - * - * @param {Object} object The object to clone. - * @param {Boolean} [deep=false] If true, all properties will be deep cloned recursively. - * @returns {Object} The cloned object. - * - * @private - */ -function clone(object, deep) { - if (object === null || typeof object !== 'object') { - return object; - } - - deep = defaultValue(deep, false); - - var isBuffer = Buffer.isBuffer(object); - var isTypedArray = Object.prototype.toString.call(object.buffer) === '[object ArrayBuffer]'; - var isArrayStorage = object instanceof ArrayStorage; - - var result; - if (isBuffer) { - result = Buffer.from(object); - return result; - } else if (isTypedArray) { - result = object.slice(); - return result; - } else if (isArrayStorage) { - result = new ArrayStorage(object.componentDatatype); - } else { - result = new object.constructor(); - } - - for (var propertyName in object) { - if (object.hasOwnProperty(propertyName)) { - var value = object[propertyName]; - if (deep) { - value = clone(value, deep); - } - result[propertyName] = value; - } - } - - return result; -} diff --git a/lib/convert.js b/lib/convert.js index 9303699..5e26300 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -16,16 +16,6 @@ var DeveloperError = Cesium.DeveloperError; module.exports = convert; -/** - * A callback function that logs messages. - * @callback Logger - * - * @param {String} message The message to log. - */ -var defaultLogger = function(message) { - console.log(message); -}; - /** * Converts an obj file to a glTF file. * @@ -36,107 +26,196 @@ var defaultLogger = function(message) { * @param {Boolean} [options.separate=false] Writes out separate geometry data files, shader files, and textures instead of embedding them in the glTF. * @param {Boolean} [options.separateTextures=false] Write out separate textures only. * @param {Boolean} [options.compress=false] Quantize positions, compress texture coordinates, and oct-encode normals. - * @param {Boolean} [options.optimize=false] Use the optimization stages in the glTF pipeline. + * @param {Boolean} [options.optimize=false] Optimize the glTF for size and runtime performance. * @param {Boolean} [options.optimizeForCesium=false] Optimize the glTF for Cesium by using the sun as a default light source. * @param {Boolean} [options.generateNormals=false] Generate normals if they are missing. * @param {Boolean} [options.ao=false] Apply ambient occlusion to the converted model. * @param {Boolean} [options.kmc=false] Output glTF with the KHR_materials_common extension. * @param {Boolean} [options.textureCompressionOptions] Options sent to the compressTextures stage of gltf-pipeline. * @param {Boolean} [options.bypassPipeline=false] Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension. - * @param {Boolean} [options.hasTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. + * @param {Boolean} [options.checkTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. * @param {Boolean} [options.secure=false] Prevent the converter from reading image or mtl files outside of the input obj directory. * @param {Logger} [options.logger] A callback function for handling logged messages. Defaults to console.log. */ function convert(objPath, gltfPath, options) { - return new Promise(function(resolve, reject) { - options = defaultValue(options, {}); - var binary = defaultValue(options.binary, false); - var separate = defaultValue(options.separate, false); - var separateTextures = defaultValue(options.separateTextures, false) || separate; - var compress = defaultValue(options.compress, false); - var optimize = defaultValue(options.optimize, false); - var optimizeForCesium = defaultValue(options.optimizeForCesium, false); - var generateNormals = defaultValue(options.generateNormals, false); - var ao = defaultValue(options.ao, false); - var kmc = defaultValue(options.kmc, false); - var textureCompressionOptions = options.textureCompressionOptions; - var bypassPipeline = defaultValue(options.bypassPipeline, false); - var logger = defaultValue(options.logger, defaultLogger); - options.logger = logger; - options.hasTransparency = defaultValue(options.hasTransparency, false); - options.secure = defaultValue(options.secure, false); + var defaults = convert.defaults; - if (!defined(objPath)) { - throw new DeveloperError('objPath is required'); - } + options = defaultValue(options, {}); + var binary = defaultValue(options.binary, defaults.binary); + var separate = defaultValue(options.separate, defaults.separate); + var separateTextures = defaultValue(options.separateTextures, defaults.separateTextures) || separate; + var compress = defaultValue(options.compress, defaults.compress); + var optimize = defaultValue(options.optimize, defaults.optimize); + var optimizeForCesium = defaultValue(options.optimizeForCesium, defaults.optimizeForCesium); + var generateNormals = defaultValue(options.generateNormals, defaults.generateNormals); + var ao = defaultValue(options.ao, defaults.ao); + var kmc = defaultValue(options.kmc, defaults.kmc); + var textureCompressionOptions = options.textureCompressionOptions; + var bypassPipeline = defaultValue(options.bypassPipeline, defaults.bypassPipeline); + var checkTransparency = defaultValue(options.checkTransparency, defaults.checkTransparency); + var secure = defaultValue(options.secure, defaults.secure); + var logger = defaultValue(options.logger, defaults.logger); - if (!defined(gltfPath)) { - throw new DeveloperError('gltfPath is required'); - } + options.separate = separate; + options.separateTextures = separateTextures; + options.checkTransparency = checkTransparency; + options.secure = secure; + options.logger = logger; - var objExtension = path.extname(objPath).toLowerCase(); - if (objExtension !== '.obj') { - throw new DeveloperError('Invalid obj path "' + objPath + '"'); - } - var extension = path.extname(gltfPath).toLowerCase(); - if (extension !== '.gltf' && extension !== '.glb') { - throw new DeveloperError('Invalid gltf path "' + gltfPath + '"'); - } + if (!defined(objPath)) { + throw new DeveloperError('objPath is required'); + } - var basePath = path.dirname(gltfPath); - var modelName = path.basename(gltfPath, path.extname(gltfPath)); - if (extension === '.glb') { - binary = true; + if (!defined(gltfPath)) { + throw new DeveloperError('gltfPath is required'); + } + + var extension = path.extname(gltfPath).toLowerCase(); + var basePath = path.dirname(gltfPath); + var modelName = path.basename(gltfPath, path.extname(gltfPath)); + if (extension === '.glb') { + binary = true; + } + + if (binary && bypassPipeline) { + throw new DeveloperError('--bypassPipeline does not convert to binary glTF'); + } + + gltfPath = path.join(path.dirname(gltfPath), modelName + extension); + + var aoOptions = ao ? {} : undefined; + var kmcOptions = kmc ? {} : undefined; + + var pipelineOptions = { + createDirectory : false, + basePath : basePath, + binary : binary, + embed : !separate, + embedImage : !separateTextures, + quantize : compress, + compressTextureCoordinates : compress, + encodeNormals : compress, + preserve : !optimize, + optimizeForCesium : optimizeForCesium, + smoothNormals : generateNormals, + aoOptions : aoOptions, + kmcOptions : kmcOptions, + textureCompressionOptions : textureCompressionOptions + }; + + return loadObj(objPath, options) + .then(function(objData) { + return createGltf(objData); + }) + .then(function(gltf) { + return writeUris(gltf, gltfPath, options); + }) + .then(function(gltf) { if (bypassPipeline) { - logger('--bypassPipeline does not convert to binary glTF, saving as .gltf'); - extension = '.gltf'; + return convert._outputJson(gltfPath, gltf); + } else { + return GltfPipeline.processJSONToDisk(gltf, gltfPath, pipelineOptions); } - } - gltfPath = path.join(path.dirname(gltfPath), modelName + extension); - - var aoOptions = ao ? {} : undefined; - var kmcOptions = kmc ? {} : undefined; - - var pipelineOptions = { - createDirectory : false, - basePath : basePath, - binary : binary, - embed : !separate, - embedImage : !separateTextures, - quantize : compress, - compressTextureCoordinates : compress, - encodeNormals : compress, - preserve : !optimize, - optimizeForCesium : optimizeForCesium, - smoothNormals : generateNormals, - aoOptions : aoOptions, - kmcOptions : kmcOptions, - textureCompressionOptions : textureCompressionOptions - }; - - return loadObj(objPath, options) - .then(function(objData) { - return createGltf(objData); - }) - .then(function(gltf) { - return writeUris(gltf, gltfPath, separate, separateTextures, logger); - }) - .then(function(gltf) { - if (bypassPipeline) { - return convert._outputJson(gltfPath, gltf); - } else { - return GltfPipeline.processJSONToDisk(gltf, gltfPath, pipelineOptions); - } - }) - .then(resolve) - .catch(reject); - }); + }); } +/** + * Default values that will be used when calling convert(options) unless specified in the options object. + */ +convert.defaults = { + /** + * Gets or sets whether the model will be saved as binary glTF. + * @type Boolean + * @default false + */ + binary: false, + /** + * Gets or sets whether to write out separate geometry/animation data files, + * shader files, and textures instead of embedding them in the glTF. + * @type Boolean + * @default false + */ + separate: false, + /** + * Gets or sets whether to write out separate textures only. + * @type Boolean + * @default false + */ + separateTextures: false, + /** + * Gets or sets whether to compress attribute data. This includes quantizing positions, compressing texture coordinates, and oct-encoding normals. + * @type Boolean + * @default false + */ + compress: false, + /** + * Gets or sets whether the model is optimized for size and runtime performance. + * @type Boolean + * @default false + */ + optimize: false, + /** + * Gets or sets whether the model is optimized for Cesium by using the sun as a default light source. + * @type Boolean + * @default false + */ + optimizeForCesium: false, + /** + * Gets or sets whether normals will be generated for the model if they are missing. + * @type Boolean + * @default false + */ + generateNormals: false, + /** + * Gets or sets whether the model will have ambient occlusion applied. + * @type Boolean + * @default false + */ + ao: false, + /** + * Gets or sets whether the model will be saved with the KHR_materials_common extension. + * @type Boolean + * @default false + */ + kmc: false, + /** + * Gets or sets whether the converter will bypass the gltf-pipeline for debugging purposes. + * @type Boolean + * @default false + */ + bypassPipeline: false, + /** + * Gets or sets whether the converter will do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. + * @type Boolean + * @default false + */ + checkTransparency: false, + /** + * Gets or sets whether the source model can reference paths outside of its directory. + * @type Boolean + * @default false + */ + secure: false, + /** + * @private + */ + logger: function(message) { + console.log(message); + } +}; + /** * Exposed for testing * * @private */ convert._outputJson = fsExtraOutputJson; + +/** + * A callback function that logs messages. + * @callback Logger + * + * @param {String} message The message to log. + */ + diff --git a/lib/gltf.js b/lib/gltf.js index 869276e..eb6aa7c 100644 --- a/lib/gltf.js +++ b/lib/gltf.js @@ -1,6 +1,7 @@ 'use strict'; var Cesium = require('cesium'); var path = require('path'); +var Material = require('./Material'); var defined = Cesium.defined; var defaultValue = Cesium.defaultValue; @@ -67,10 +68,10 @@ function createGltf(objData) { } function createMaterial(material, hasNormals) { - var ambient = defaultValue(defaultValue(getTextureId(material.ambientColorMap), material.ambientColor), [0, 0, 0, 1]); - var diffuse = defaultValue(defaultValue(getTextureId(material.diffuseColorMap), material.diffuseColor), [0.5, 0.5, 0.5, 1]); - var emission = defaultValue(defaultValue(getTextureId(material.emissionColorMap), material.emissionColor), [0, 0, 0, 1]); - var specular = defaultValue(defaultValue(getTextureId(material.specularColorMap), material.specularColor), [0, 0, 0, 1]); + var ambient = defaultValue(defaultValue(getTextureId(material.ambientTexture), material.ambientColor)); + var diffuse = defaultValue(defaultValue(getTextureId(material.diffuseTexture), material.diffuseColor)); + var emission = defaultValue(defaultValue(getTextureId(material.emissionTexture), material.emissionColor)); + var specular = defaultValue(defaultValue(getTextureId(material.specularTexture), material.specularColor)); var alpha = defaultValue(defaultValue(material.alpha), 1.0); var shininess = defaultValue(material.specularShininess, 0.0); var hasSpecular = (shininess > 0.0) && (specular[0] > 0.0 || specular[1] > 0.0 || specular[2] > 0.0); @@ -79,7 +80,7 @@ function createGltf(objData) { var transparency = 1.0; if (typeof diffuse === 'string') { transparency = alpha; - transparent = images[material.diffuseColorMap].transparent || (transparency < 1.0); + transparent = images[material.diffuseTexture].transparent || (transparency < 1.0); } else { diffuse[3] = alpha; transparent = diffuse[3] < 1.0; @@ -93,11 +94,6 @@ function createGltf(objData) { diffuse = [0, 0, 0, 1]; } - // It's not completely clear whether transparent and doubleSided belong under values or KHR_materials_common - // Put under both for now to handle both situations. - // https://github.com/KhronosGroup/glTF/tree/master/extensions/Khronos/KHR_materials_common - // https://github.com/KhronosGroup/glTF/issues/632 - var technique = hasNormals ? (hasSpecular ? 'PHONG' : 'LAMBERT') : 'CONSTANT'; return { extensions : { @@ -287,7 +283,8 @@ function createGltf(objData) { materialId = 'default'; } - var material = defaultValue(materials[materialId], {}); + var material = materials[materialId]; + material = defined(material) ? material : new Material(); var gltfMaterial = gltf.materials[materialId]; if (defined(gltfMaterial)) { // Check if this material has already been added but with incompatible shading diff --git a/lib/image.js b/lib/image.js index e5cbc45..8a3b686 100644 --- a/lib/image.js +++ b/lib/image.js @@ -7,7 +7,7 @@ var Promise = require('bluebird'); var fsExtraReadFile = Promise.promisify(fsExtra.readFile); -var defaultValue = Cesium.defaultValue; +var defined = Cesium.defined; var WebGLConstants = Cesium.WebGLConstants; module.exports = loadImage; @@ -16,19 +16,16 @@ module.exports = loadImage; * Load an image file and get information about it. * * @param {String} imagePath Path to the image file. - * @param {Object} [options] An object with the following properties: - * @param {Boolean} [options.hasTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. + * @param {Object} options An object with the following properties: + * @param {Boolean} options.checkTransparency Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. * @returns {Promise} A promise resolving to the image information, or undefined if the file doesn't exist. * * @private */ function loadImage(imagePath, options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - var hasTransparency = defaultValue(options.hasTransparency, false); - return fsExtraReadFile(imagePath) .then(function(data) { - var extension = path.extname(imagePath); + var extension = path.extname(imagePath).toLowerCase(); var info = { transparent : false, @@ -38,31 +35,35 @@ function loadImage(imagePath, options) { }; if (extension === '.png') { - // Color type is encoded in the 25th bit of the png - var colorType = data[25]; - var channels = getChannels(colorType); - info.format = getFormat(channels); - - if (channels === 4) { - info.transparent = true; - if (hasTransparency) { - return isTransparent(data) - .then(function(transparent) { - info.transparent = transparent; - return info; - }); - } - } + return getPngInfo(data, info, options); } return info; }); } +function getPngInfo(data, info, options) { + // Color type is encoded in the 25th bit of the png + var colorType = data[25]; + var channels = getChannels(colorType); + info.format = getFormat(channels); + + if (channels === 4) { + if (options.checkTransparency) { + return isTransparent(data) + .then(function(transparent) { + info.transparent = transparent; + return info; + }); + } + } + return info; +} + function isTransparent(data) { return new Promise(function(resolve, reject) { new PNG().parse(data, function(error, data) { - if (error) { + if (defined(error)) { reject(error); return; } diff --git a/lib/mtl.js b/lib/mtl.js index 4ced92e..6d3f506 100644 --- a/lib/mtl.js +++ b/lib/mtl.js @@ -1,30 +1,15 @@ 'use strict'; var path = require('path'); +var Material = require('./Material'); var readLines = require('./readLines'); module.exports = loadMtl; -function Material() { - this.ambientColor = undefined; // Ka - this.emissionColor = undefined; // Ke - this.diffuseColor = undefined; // Kd - this.specularColor = undefined; // Ks - this.specularShininess = undefined; // Ns - this.alpha = undefined; // d / Tr - this.ambientColorMap = undefined; // map_Ka - this.emissionColorMap = undefined; // map_Ke - this.diffuseColorMap = undefined; // map_Kd - this.specularColorMap = undefined; // map_Ks - this.specularShininessMap = undefined; // map_Ns - this.normalMap = undefined; // map_Bump - this.alphaMap = undefined; // map_d -} - /** * Parse an mtl file. * * @param {String} mtlPath Path to the mtl file. - * @returns {Promise} A promise resolving to the materials, or an empty object if the mtl file doesn't exist. + * @returns {Promise} A promise resolving to the materials. * * @private */ @@ -32,6 +17,7 @@ function loadMtl(mtlPath) { var material; var values; var value; + var mtlDirectory = path.dirname(mtlPath); var materials = {}; function parseLine(line) { @@ -82,19 +68,19 @@ function loadMtl(mtlPath) { value = line.substring(3).trim(); material.alpha = parseFloat(value); } else if (/^map_Ka /i.test(line)) { - material.ambientColorMap = getAbsolutePath(line.substring(7).trim(), mtlPath); + material.ambientTexture = path.resolve(mtlDirectory, line.substring(7).trim()); } else if (/^map_Ke /i.test(line)) { - material.emissionColorMap = getAbsolutePath(line.substring(7).trim(), mtlPath); + material.emissionTexture = path.resolve(mtlDirectory, line.substring(7).trim()); } else if (/^map_Kd /i.test(line)) { - material.diffuseColorMap = getAbsolutePath(line.substring(7).trim(), mtlPath); + material.diffuseTexture = path.resolve(mtlDirectory, line.substring(7).trim()); } else if (/^map_Ks /i.test(line)) { - material.specularColorMap = getAbsolutePath(line.substring(7).trim(), mtlPath); + material.specularTexture = path.resolve(mtlDirectory, line.substring(7).trim()); } else if (/^map_Ns /i.test(line)) { - material.specularShininessMap = getAbsolutePath(line.substring(7).trim(), mtlPath); + material.specularShininessMap = path.resolve(mtlDirectory, line.substring(7).trim()); } else if (/^map_Bump /i.test(line)) { - material.normalMap = getAbsolutePath(line.substring(9).trim(), mtlPath); + material.normalMap = path.resolve(mtlDirectory, line.substring(9).trim()); } else if (/^map_d /i.test(line)) { - material.alphaMap = getAbsolutePath(line.substring(6).trim(), mtlPath); + material.alphaMap = path.resolve(mtlDirectory, line.substring(6).trim()); } } @@ -103,10 +89,3 @@ function loadMtl(mtlPath) { return materials; }); } - -function getAbsolutePath(imagePath, mtlPath) { - if (!path.isAbsolute(imagePath)) { - imagePath = path.join(path.dirname(mtlPath), imagePath); - } - return imagePath; -} diff --git a/lib/obj.js b/lib/obj.js index 63ab31d..cfd1d7c 100644 --- a/lib/obj.js +++ b/lib/obj.js @@ -8,7 +8,6 @@ var loadImage = require('./image'); var loadMtl = require('./mtl'); var readLines = require('./readLines'); -var combine = Cesium.combine; var ComponentDatatype = Cesium.ComponentDatatype; var defaultValue = Cesium.defaultValue; var defined = Cesium.defined; @@ -51,25 +50,16 @@ var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/( * Parse an obj file. * * @param {String} objPath Path to the obj file. - * @param {Object} [options] An object with the following properties: - * @param {Boolean} [options.hasTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. - * @param {Boolean} [options.secure=false] Prevent the converter from reading image or mtl files outside of the input obj directory. - * @param {Boolean} [options.logger] A callback function for handling logged messages. Defaults to console.log. + * @param {Object} options An object with the following properties: + * @param {Boolean} options.checkTransparency Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. + * @param {Boolean} options.secure Prevent the converter from reading image or mtl files outside of the input obj directory. + * @param {Boolean} options.logger A callback function for handling logged messages. Defaults to console.log. * @returns {Promise} A promise resolving to the obj data. * @exception {RuntimeError} The file does not have any geometry information in it. * * @private */ function loadObj(objPath, options) { - // The defaults are set in convert as well, this just helps with testing loadObj individually - options = combine(options, { - hasTransparency : false, - secure : false, - logger : function(message) { - console.log(message); - } - }); - // Global store of vertex attributes listed in the obj file var positions = new ArrayStorage(ComponentDatatype.FLOAT); var normals = new ArrayStorage(ComponentDatatype.FLOAT); @@ -293,7 +283,7 @@ function loadObj(objPath, options) { function finishLoading(nodes, mtlPaths, objPath, options) { nodes = cleanNodes(nodes); if (nodes.length === 0) { - throw new RuntimeError(objPath + ' does not have any geometry data'); + return Promise.reject(new RuntimeError(objPath + ' does not have any geometry data')); } return loadMaterials(mtlPaths, objPath, options) .then(function(materials) { @@ -309,57 +299,50 @@ function finishLoading(nodes, mtlPaths, objPath, options) { }); } -function getAbsolutePath(mtlPath, objPath) { - if (!path.isAbsolute(mtlPath)) { - mtlPath = path.join(path.dirname(objPath), mtlPath); - } - return mtlPath; -} - function outsideDirectory(filePath, objPath) { return (path.relative(path.dirname(objPath), filePath).indexOf('..') === 0); } function loadMaterials(mtlPaths, objPath, options) { + var secure = options.secure; + var logger = options.logger; + var objDirectory = path.dirname(objPath); var materials = {}; return Promise.map(mtlPaths, function(mtlPath) { - mtlPath = getAbsolutePath(mtlPath, objPath); - if (options.secure && outsideDirectory(mtlPath, objPath)) { - options.logger('Could not read mtl file at ' + mtlPath + ' because it is outside of the obj directory and the secure flag is true. Using default material instead.'); + mtlPath = path.resolve(objDirectory, mtlPath); + if (secure && outsideDirectory(mtlPath, objPath)) { + logger('Could not read mtl file at ' + mtlPath + ' because it is outside of the obj directory and the secure flag is true. Using default material instead.'); return; } return loadMtl(mtlPath) .then(function(materialsInMtl) { - materials = combine(materials, materialsInMtl); + materials = Object.assign(materials, materialsInMtl); }) .catch(function() { - options.logger('Could not read mtl file at ' + mtlPath + '. Using default material instead.'); + logger('Could not read mtl file at ' + mtlPath + '. Using default material instead.'); }); - }).then(function() { - return materials; - }); + }, {concurrency : 10}) + .thenReturn(materials); } function loadImages(imagePaths, objPath, options) { + var secure = options.secure; + var logger = options.logger; var images = {}; return Promise.map(imagePaths, function(imagePath) { - if (options.secure && outsideDirectory(imagePath, objPath)) { - options.logger('Could not read image file at ' + imagePath + ' because it is outside of the obj directory and the secure flag is true. Material will ignore this image.'); + if (secure && outsideDirectory(imagePath, objPath)) { + logger('Could not read image file at ' + imagePath + ' because it is outside of the obj directory and the secure flag is true. Material will ignore this image.'); return; } return loadImage(imagePath, options) .then(function(image) { - if (defined(image)) { - images[imagePath] = image; - } + images[imagePath] = image; }) .catch(function() { - options.logger('Could not read image file at ' + imagePath + '. Material will ignore this image.'); - return undefined; + logger('Could not read image file at ' + imagePath + '. Material will ignore this image.'); }); - }).then(function() { - return images; - }); + }, {concurrency : 10}) + .thenReturn(images); } function getImagePaths(materials) { @@ -367,46 +350,32 @@ function getImagePaths(materials) { for (var name in materials) { if (materials.hasOwnProperty(name)) { var material = materials[name]; - if (defined(material.ambientColorMap)) { - imagePaths[material.ambientColorMap] = true; + if (defined(material.ambientTexture)) { + imagePaths[material.ambientTexture] = true; } - if (defined(material.diffuseColorMap)) { - imagePaths[material.diffuseColorMap] = true; + if (defined(material.diffuseTexture)) { + imagePaths[material.diffuseTexture] = true; } - if (defined(material.emissionColorMap)) { - imagePaths[material.emissionColorMap] = true; + if (defined(material.emissionTexture)) { + imagePaths[material.emissionTexture] = true; } - if (defined(material.specularColorMap)) { - imagePaths[material.specularColorMap] = true; + if (defined(material.specularTexture)) { + imagePaths[material.specularTexture] = true; } } } return Object.keys(imagePaths); } -function removeEmptyPrimitives(primitives) { - var final = []; - var primitivesLength = primitives.length; - for (var i = 0; i < primitivesLength; ++i) { - var primitive = primitives[i]; - if (primitive.indices.length > 0) { - final.push(primitive); - } - } - return final; -} - function removeEmptyMeshes(meshes) { - var final = []; - var meshesLength = meshes.length; - for (var i = 0; i < meshesLength; ++i) { - var mesh = meshes[i]; - mesh.primitives = removeEmptyPrimitives(mesh.primitives); - if ((mesh.primitives.length > 0) && (mesh.positions.length > 0)) { - final.push(mesh); - } - } - return final; + return meshes.filter(function(mesh) { + // Remove empty primitives + mesh.primitives = mesh.primitives.filter(function(primitive) { + return primitive.indices.length > 0; + }); + // Valid meshes must have at least one primitive and contain positions + return (mesh.primitives.length > 0) && (mesh.positions.length > 0); + }); } function meshesHaveNames(meshes) { diff --git a/lib/writeUris.js b/lib/writeUris.js index af88ec7..53e23c3 100644 --- a/lib/writeUris.js +++ b/lib/writeUris.js @@ -1,4 +1,5 @@ 'use strict'; +var Cesium = require('cesium'); var fsExtra = require('fs-extra'); var mime = require('mime'); var path = require('path'); @@ -6,6 +7,8 @@ var Promise = require('bluebird'); var fsExtraOutputFile = Promise.promisify(fsExtra.outputFile); +var RuntimeError = Cesium.RuntimeError; + module.exports = writeUris; /** @@ -13,14 +16,17 @@ module.exports = writeUris; * * @param {Object} gltf The glTF asset. * @param {String} gltfPath Path where the glTF will be saved. - * @param {Boolean} separateBuffers Writes out separate buffers. - * @param {Boolean} separateTextures Writes out separate textures. - * @param {Logger} logger A callback function for handling logged messages. Defaults to console.log. + * @param {Object} options An object with the following properties: + * @param {Boolean} options.separate Writes out separate buffers. + * @param {Boolean} options.separateTextures Write out separate textures only. * @returns {Promise} A promise that resolves to the glTF asset. * * @private */ -function writeUris(gltf, gltfPath, separateBuffers, separateTextures, logger) { +function writeUris(gltf, gltfPath, options) { + var separate = options.separate; + var separateTextures = options.separateTextures; + var promises = []; var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; @@ -37,17 +43,17 @@ function writeUris(gltf, gltfPath, separateBuffers, separateTextures, logger) { // Buffers larger than ~192MB cannot be base64 encoded due to a NodeJS limitation. Source: https://github.com/nodejs/node/issues/4266 var exceedsMaximum = (texturesByteLength + bufferByteLength > 201326580); - if (exceedsMaximum) { - logger('Buffers and textures are too large to encode in the glTF, saving as separate resources.'); + if (exceedsMaximum && !separate) { + return Promise.reject(new RuntimeError('Buffers and textures are too large to encode in the glTF, saving as separate resources.')); } - if (separateBuffers || exceedsMaximum) { + if (separate) { promises.push(writeSeparateBuffer(gltf, gltfPath)); } else { writeEmbeddedBuffer(gltf); } - if (separateTextures || exceedsMaximum) { + if (separateTextures) { promises.push(writeSeparateTextures(gltf, gltfPath)); } else { writeEmbeddedTextures(gltf); @@ -84,19 +90,15 @@ function writeSeparateBuffer(gltf, gltfPath) { } function writeSeparateTextures(gltf, gltfPath) { - var promises = []; var images = gltf.images; - for (var id in images) { - if (images.hasOwnProperty(id)) { - var image = images[id]; - var extras = image.extras._obj2gltf; - var imageUri = image.name + extras.extension; - image.uri = imageUri; - var imagePath = path.join(path.dirname(gltfPath), imageUri); - promises.push(writeUris._outputFile(imagePath, extras.source)); - } - } - return Promise.all(promises); + return Promise.map(Object.keys(images), function(id) { + var image = images[id]; + var extras = image.extras._obj2gltf; + var imageUri = image.name + extras.extension; + image.uri = imageUri; + var imagePath = path.join(path.dirname(gltfPath), imageUri); + return writeUris._outputFile(imagePath, extras.source); + }, {concurrency : 10}); } function writeEmbeddedBuffer(gltf) { @@ -106,7 +108,6 @@ function writeEmbeddedBuffer(gltf) { } function writeEmbeddedTextures(gltf) { - var promises = []; var images = gltf.images; for (var id in images) { if (images.hasOwnProperty(id)) { @@ -115,7 +116,6 @@ function writeEmbeddedTextures(gltf) { image.uri = 'data:' + mime.lookup(extras.extension) + ';base64,' + extras.source.toString('base64'); } } - return Promise.all(promises); } /** diff --git a/specs/lib/convertSpec.js b/specs/lib/convertSpec.js index 35fd0eb..2d0476e 100644 --- a/specs/lib/convertSpec.js +++ b/specs/lib/convertSpec.js @@ -1,20 +1,13 @@ 'use strict'; -var Cesium = require('cesium'); var GltfPipeline = require('gltf-pipeline').Pipeline; var path = require('path'); var convert = require('../../lib/convert'); var writeUris = require('../../lib/writeUris'); -var DeveloperError = Cesium.DeveloperError; - var objPath = 'specs/data/box-textured/box-textured.obj'; var gltfPath = 'specs/data/box-textured/box-textured.gltf'; var glbPath = 'specs/data/box-textured/box-textured.glb'; -var objPathInvalid = 'invalid/'; -var gltfPathInvalid = 'invalid/model.invalid'; var objPathNonExistent = 'specs/data/non-existent.obj'; -var gltfPathNonExistent = 'specs/data/non-existent.gltf'; - var objExternalResourcesPath = 'specs/data/box-external-resources/box-external-resources.obj'; describe('convert', function() { @@ -57,8 +50,8 @@ describe('convert', function() { }); it('sets options', function(done) { - var spy1 = spyOn(GltfPipeline, 'processJSONToDisk'); - var spy2 = spyOn(writeUris, '_outputFile'); + var spy = spyOn(GltfPipeline, 'processJSONToDisk'); + spyOn(writeUris, '_outputFile'); var textureCompressionOptions = { format : 'dxt1', quality : 10 @@ -69,16 +62,19 @@ describe('convert', function() { separateTextures : true, compress : true, optimize : true, + optimizeForCesium : true, generateNormals : true, ao : true, kmc : true, - optimizeForCesium : true, - textureCompressionOptions : textureCompressionOptions + textureCompressionOptions : textureCompressionOptions, + checkTransparency : true, + secure : true, + logger : convert.defaults.logger }; expect(convert(objPath, gltfPath, options) .then(function() { - var args = spy1.calls.first().args; + var args = spy.calls.first().args; var options = args[2]; expect(options).toEqual({ createDirectory : false, @@ -96,7 +92,7 @@ describe('convert', function() { textureCompressionOptions : textureCompressionOptions, preserve : false }); - expect(spy2.calls.count()).toBe(2); // Saves out .png and .bin + expect(writeUris._outputFile.calls.count()).toBe(2); // Saves out .png and .bin }), done).toResolve(); }); @@ -111,50 +107,31 @@ describe('convert', function() { }); it('bypassPipeline flag bypasses gltf-pipeline', function(done) { - var spy1 = spyOn(convert, '_outputJson'); - var spy2 = spyOn(GltfPipeline, 'processJSONToDisk'); + spyOn(convert, '_outputJson'); + spyOn(GltfPipeline, 'processJSONToDisk'); var options = { bypassPipeline : true }; expect(convert(objPath, gltfPath, options) .then(function() { - expect(spy1.calls.count()).toBe(1); - expect(spy2.calls.count()).toBe(0); + expect(convert._outputJson).toHaveBeenCalled(); + expect(GltfPipeline.processJSONToDisk).not.toHaveBeenCalled(); }), done).toResolve(); }); - it('uses a custom logger', function(done) { - var spy = spyOn(GltfPipeline, 'processJSONToDisk'); - var logCount = 0; - var options = { - secure : true, // Needs to be set to trigger messages - logger : function() { - logCount++; - } - }; - expect(convert(objExternalResourcesPath, gltfPath, options) - .then(function() { - expect(logCount).toEqual(2); - }), done).toResolve(); - }); - - it('rejects if objPath is undefined', function(done) { - expect(convert(undefined, gltfPath), done).toRejectWith(DeveloperError); - }); - - it('rejects if gltfPath is undefined', function(done) { - expect(convert(objPath, undefined), done).toRejectWith(DeveloperError); - }); - - it('rejects if obj path is invalid', function(done) { - expect(convert(objPathInvalid, gltfPath), done).toRejectWith(DeveloperError); - }); - - it('rejects if gltf path is invalid', function(done) { - expect(convert(objPath, gltfPathInvalid), done).toRejectWith(DeveloperError); - }); - it('rejects if obj path does not exist', function(done) { expect(convert(objPathNonExistent, gltfPath), done).toRejectWith(Error); }); + + it('throws if objPath is undefined', function() { + expect(function() { + convert(undefined, gltfPath); + }).toThrowDeveloperError(); + }); + + it('rejects if gltfPath is undefined', function() { + expect(function() { + convert(objPath, undefined); + }).toThrowDeveloperError(); + }); }); diff --git a/specs/lib/gltfSpec.js b/specs/lib/gltfSpec.js index 297918e..b139ada 100644 --- a/specs/lib/gltfSpec.js +++ b/specs/lib/gltfSpec.js @@ -3,12 +3,14 @@ var Cesium = require('cesium'); var fsExtra = require('fs-extra'); var path = require('path'); var Promise = require('bluebird'); -var clone = require('../../lib/clone.js'); -var createGltf = require('../../lib/gltf.js'); -var loadImage = require('../../lib/image.js'); -var loadObj = require('../../lib/obj.js'); -var writeUris = require('../../lib/writeUris.js'); +var convert = require('../../lib/convert'); +var createGltf = require('../../lib/gltf'); +var loadImage = require('../../lib/image'); +var loadObj = require('../../lib/obj'); +var Material = require('../../lib/Material'); +var writeUris = require('../../lib/writeUris'); +var clone = Cesium.clone; var WebGLConstants = Cesium.WebGLConstants; var fsExtraReadJson = Promise.promisify(fsExtra.readJson); @@ -20,21 +22,30 @@ var groupGltfUrl = 'specs/data/box-objects-groups-materials/box-objects-groups-m var diffuseTextureUrl = 'specs/data/box-textured/cesium.png'; var transparentDiffuseTextureUrl = 'specs/data/box-complex-material/diffuse.png'; +var defaultOptions = convert.defaults; +var checkTransparencyOptions = clone(defaultOptions); +checkTransparencyOptions.checkTransparency = true; + describe('gltf', function() { var boxObjData; + var duplicateBoxObjData; var groupObjData; var boxGltf; var groupGltf; var diffuseTexture; var transparentDiffuseTexture; - beforeAll(function(done) { + beforeEach(function(done) { return Promise.all([ - loadObj(boxObjUrl) + loadObj(boxObjUrl, defaultOptions) .then(function(data) { boxObjData = data; }), - loadObj(groupObjUrl) + loadObj(boxObjUrl, defaultOptions) + .then(function(data) { + duplicateBoxObjData = data; + }), + loadObj(groupObjUrl, defaultOptions) .then(function(data) { groupObjData = data; }), @@ -46,11 +57,11 @@ describe('gltf', function() { .then(function(gltf) { groupGltf = gltf; }), - loadImage(diffuseTextureUrl) + loadImage(diffuseTextureUrl, defaultOptions) .then(function(image) { diffuseTexture = image; }), - loadImage(transparentDiffuseTextureUrl) + loadImage(transparentDiffuseTextureUrl, checkTransparencyOptions) .then(function(image) { transparentDiffuseTexture = image; }) @@ -58,19 +69,17 @@ describe('gltf', function() { }); it('simple gltf', function(done) { - var objData = clone(boxObjData, true); - var gltf = createGltf(objData); - expect(writeUris(gltf, boxGltfUrl, false, false) + var gltf = createGltf(boxObjData); + expect(writeUris(gltf, boxGltfUrl, defaultOptions) .then(function() { expect(gltf).toEqual(boxGltf); }), done).toResolve(); }); it('multiple nodes, meshes, and primitives', function(done) { - var objData = clone(groupObjData, true); - var gltf = createGltf(objData); + var gltf = createGltf(groupObjData); - expect(writeUris(gltf, groupGltfUrl, false, false) + expect(writeUris(gltf, groupGltfUrl, defaultOptions) .then(function() { expect(gltf).toEqual(groupGltf); expect(Object.keys(gltf.materials).length).toBe(3); @@ -88,10 +97,9 @@ describe('gltf', function() { }); it('sets default material values', function() { - var objData = clone(boxObjData, true); - objData.materials.Material = {}; + boxObjData.materials.Material = new Material(); - var gltf = createGltf(objData); + var gltf = createGltf(boxObjData); var material = gltf.materials.Material; var kmc = material.extensions.KHR_materials_common; var values = kmc.values; @@ -105,13 +113,12 @@ describe('gltf', function() { }); it('sets material for diffuse texture', function() { - var objData = clone(boxObjData, true); - objData.materials.Material = { - diffuseColorMap : diffuseTextureUrl - }; - objData.images[diffuseTextureUrl] = diffuseTexture; + var material = new Material(); + material.diffuseTexture = diffuseTextureUrl; + boxObjData.materials.Material = material; + boxObjData.images[diffuseTextureUrl] = diffuseTexture; - var gltf = createGltf(objData); + var gltf = createGltf(boxObjData); var kmc = gltf.materials.Material.extensions.KHR_materials_common; var texture = gltf.textures.texture_cesium; var image = gltf.images.cesium; @@ -145,12 +152,11 @@ describe('gltf', function() { }); it('sets material for alpha less than 1', function() { - var objData = clone(boxObjData, true); - objData.materials.Material = { - alpha : 0.4 - }; + var material = new Material(); + material.alpha = 0.4; + boxObjData.materials.Material = material; - var gltf = createGltf(objData); + var gltf = createGltf(boxObjData); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 0.4]); @@ -160,14 +166,14 @@ describe('gltf', function() { }); it('sets material for diffuse texture and alpha less than 1', function() { - var objData = clone(boxObjData, true); - objData.materials.Material = { - diffuseColorMap : diffuseTextureUrl, - alpha : 0.4 - }; - objData.images[diffuseTextureUrl] = diffuseTexture; + var material = new Material(); + material.diffuseTexture = diffuseTextureUrl; + material.alpha = 0.4; + boxObjData.materials.Material = material; - var gltf = createGltf(objData); + boxObjData.images[diffuseTextureUrl] = diffuseTexture; + + var gltf = createGltf(boxObjData); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.values.diffuse).toEqual('texture_cesium'); @@ -177,13 +183,13 @@ describe('gltf', function() { }); it('sets material for transparent diffuse texture', function() { - var objData = clone(boxObjData, true); - objData.materials.Material = { - diffuseColorMap : transparentDiffuseTextureUrl - }; - objData.images[transparentDiffuseTextureUrl] = transparentDiffuseTexture; + var material = new Material(); + material.diffuseTexture = transparentDiffuseTextureUrl; + boxObjData.materials.Material = material; - var gltf = createGltf(objData); + boxObjData.images[transparentDiffuseTextureUrl] = transparentDiffuseTexture; + + var gltf = createGltf(boxObjData); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.values.diffuse).toBe('texture_diffuse'); @@ -193,13 +199,12 @@ describe('gltf', function() { }); it('sets material for specular', function() { - var objData = clone(boxObjData, true); - objData.materials.Material = { - specularColor : [0.1, 0.1, 0.2, 1], - specularShininess : 0.1 - }; + var material = new Material(); + material.specularColor = [0.1, 0.1, 0.2, 1]; + material.specularShininess = 0.1; + boxObjData.materials.Material = material; - var gltf = createGltf(objData); + var gltf = createGltf(boxObjData); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.technique).toBe('PHONG'); @@ -208,14 +213,15 @@ describe('gltf', function() { }); it('sets constant material when there are no normals', function() { - var objData = clone(boxObjData, true); - objData.nodes[0].meshes[0].normals.length = 0; - objData.materials.Material = { - diffuseColorMap : diffuseTextureUrl - }; - objData.images[diffuseTextureUrl] = diffuseTexture; + boxObjData.nodes[0].meshes[0].normals.length = 0; - var gltf = createGltf(objData); + var material = new Material(); + material.diffuseTexture = diffuseTextureUrl; + boxObjData.materials.Material = material; + + boxObjData.images[diffuseTextureUrl] = diffuseTexture; + + var gltf = createGltf(boxObjData); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.technique).toBe('CONSTANT'); @@ -223,70 +229,66 @@ describe('gltf', function() { }); it('sets default material when texture is missing', function() { - var objData = clone(boxObjData, true); - objData.materials.Material = { - diffuseColorMap : diffuseTextureUrl - }; + var material = new Material(); + material.diffuseTexture = diffuseTextureUrl; + boxObjData.materials.Material = material; - var gltf = createGltf(objData); + var gltf = createGltf(boxObjData); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]); }); it('uses default material (1)', function() { - var objData = clone(boxObjData, true); - objData.nodes[0].meshes[0].primitives[0].material = undefined; + boxObjData.nodes[0].meshes[0].primitives[0].material = undefined; // Creates a material called "default" - var gltf = createGltf(objData); + var gltf = createGltf(boxObjData); expect(gltf.materials.default).toBeDefined(); var kmc = gltf.materials.default.extensions.KHR_materials_common; expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]); }); it('uses default material (2)', function() { - var objData = clone(boxObjData, true); - objData.materials = {}; + boxObjData.materials = {}; // Uses the original name of the material - var gltf = createGltf(objData); + var gltf = createGltf(boxObjData); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]); }); - it('handles material used with and without normals', function() { + it('handles material used with and without normals (1)', function() { // Two meshes - one with normals, and one without - var objData = clone(boxObjData, true); - objData.nodes.push(clone(objData.nodes[0], true)); - objData.nodes[1].meshes[0].normals.length = 0; + boxObjData.nodes.push(duplicateBoxObjData.nodes[0]); + boxObjData.nodes[1].meshes[0].normals.length = 0; - var gltf = createGltf(objData); + var gltf = createGltf(boxObjData); var kmc1 = gltf.materials.Material.extensions.KHR_materials_common; var kmc2 = gltf.materials.Material_constant.extensions.KHR_materials_common; expect(kmc1.technique).toBe('PHONG'); expect(kmc2.technique).toBe('CONSTANT'); + }); + it('handles material used with and without normals (2)', function() { // Now test in a different order - objData = clone(boxObjData, true); - objData.nodes.push(clone(objData.nodes[0], true)); - objData.nodes[0].meshes[0].normals.length = 0; + boxObjData.nodes.push(duplicateBoxObjData.nodes[0]); + boxObjData.nodes[0].meshes[0].normals.length = 0; - gltf = createGltf(objData); - kmc1 = gltf.materials.Material.extensions.KHR_materials_common; - kmc2 = gltf.materials.Material_shaded.extensions.KHR_materials_common; + var gltf = createGltf(boxObjData); + var kmc1 = gltf.materials.Material.extensions.KHR_materials_common; + var kmc2 = gltf.materials.Material_shaded.extensions.KHR_materials_common; expect(kmc1.technique).toBe('CONSTANT'); expect(kmc2.technique).toBe('PHONG'); }); it('runs without normals', function() { - var objData = clone(boxObjData, true); - objData.nodes[0].meshes[0].normals.length = 0; + boxObjData.nodes[0].meshes[0].normals.length = 0; - var gltf = createGltf(objData); + var gltf = createGltf(boxObjData); var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes; expect(attributes.POSITION).toBeDefined(); expect(attributes.NORMAL).toBeUndefined(); @@ -294,10 +296,9 @@ describe('gltf', function() { }); it('runs without uvs', function() { - var objData = clone(boxObjData, true); - objData.nodes[0].meshes[0].uvs.length = 0; + boxObjData.nodes[0].meshes[0].uvs.length = 0; - var gltf = createGltf(objData); + var gltf = createGltf(boxObjData); var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes; expect(attributes.POSITION).toBeDefined(); expect(attributes.NORMAL).toBeDefined(); @@ -305,11 +306,10 @@ describe('gltf', function() { }); it('runs without uvs and normals', function() { - var objData = clone(boxObjData, true); - objData.nodes[0].meshes[0].normals.length = 0; - objData.nodes[0].meshes[0].uvs.length = 0; + boxObjData.nodes[0].meshes[0].normals.length = 0; + boxObjData.nodes[0].meshes[0].uvs.length = 0; - var gltf = createGltf(objData); + var gltf = createGltf(boxObjData); var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes; expect(attributes.POSITION).toBeDefined(); expect(attributes.NORMAL).toBeUndefined(); @@ -344,13 +344,12 @@ describe('gltf', function() { } it('detects need to use uint32 indices', function() { - var objData = clone(boxObjData, true); - expandObjData(objData, 2731); // Right above 65536 limit - var mesh = objData.nodes[0].meshes[0]; + expandObjData(boxObjData, 2731); // Right above 65536 limit + var mesh = boxObjData.nodes[0].meshes[0]; var indicesLength = mesh.primitives[0].indices.length; var vertexCount = mesh.positions.length / 3; - var gltf = createGltf(objData); + var gltf = createGltf(boxObjData); var primitive = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0]; var indicesAccessor = gltf.accessors[primitive.indices]; expect(indicesAccessor.count).toBe(indicesLength); diff --git a/specs/lib/imageSpec.js b/specs/lib/imageSpec.js index e1eff09..452a8c0 100644 --- a/specs/lib/imageSpec.js +++ b/specs/lib/imageSpec.js @@ -1,8 +1,9 @@ 'use strict'; var Cesium = require('cesium'); -var path = require('path'); -var loadImage = require('../../lib/image.js'); +var convert = require('../../lib/convert'); +var loadImage = require('../../lib/image'); +var clone = Cesium.clone; var WebGLConstants = Cesium.WebGLConstants; var pngImage = 'specs/data/box-complex-material/shininess.png'; @@ -11,12 +12,12 @@ var jpegImage = 'specs/data/box-complex-material/specular.jpeg'; var gifImage = 'specs/data/box-complex-material/ambient.gif'; var grayscaleImage = 'specs/data/box-complex-material/alpha.png'; var transparentImage = 'specs/data/box-complex-material/diffuse.png'; -var opaqueAlphaImage = 'specs/data/box-complex-material/bump.png'; -var invalidImage = 'invalid.png'; + +var defaultOptions = convert.defaults; describe('image', function() { it('loads png image', function(done) { - expect(loadImage(pngImage) + expect(loadImage(pngImage, defaultOptions) .then(function(info) { expect(info.transparent).toBe(false); expect(info.format).toBe(WebGLConstants.RGB); @@ -26,7 +27,7 @@ describe('image', function() { }); it('loads jpg image', function(done) { - expect(loadImage(jpgImage) + expect(loadImage(jpgImage, defaultOptions) .then(function(info) { expect(info.transparent).toBe(false); expect(info.format).toBe(WebGLConstants.RGB); @@ -36,7 +37,7 @@ describe('image', function() { }); it('loads jpeg image', function(done) { - expect(loadImage(jpegImage) + expect(loadImage(jpegImage, defaultOptions) .then(function(info) { expect(info.transparent).toBe(false); expect(info.format).toBe(WebGLConstants.RGB); @@ -46,7 +47,7 @@ describe('image', function() { }); it('loads gif image', function(done) { - expect(loadImage(gifImage) + expect(loadImage(gifImage, defaultOptions) .then(function(info) { expect(info.transparent).toBe(false); expect(info.format).toBe(WebGLConstants.RGB); @@ -56,7 +57,7 @@ describe('image', function() { }); it('loads grayscale image', function(done) { - expect(loadImage(grayscaleImage) + expect(loadImage(grayscaleImage, defaultOptions) .then(function(info) { expect(info.transparent).toBe(false); expect(info.format).toBe(WebGLConstants.ALPHA); @@ -65,30 +66,20 @@ describe('image', function() { }), done).toResolve(); }); - it('loads transparent image', function(done) { - expect(loadImage(transparentImage) - .then(function(info) { - expect(info.transparent).toBe(true); - expect(info.format).toBe(WebGLConstants.RGBA); - expect(info.source).toBeDefined(); - expect(info.extension).toBe('.png'); - }), done).toResolve(); - }); - - it('loads image with fully opaque alpha channel', function(done) { - expect(loadImage(opaqueAlphaImage) - .then(function(info) { - expect(info.transparent).toBe(true); - }), done).toResolve(); - }); - - it('loads image with fully opaque alpha channel with hasTransparency flag', function(done) { - var options = { - hasTransparency : true - }; - expect(loadImage(opaqueAlphaImage, options) + it('loads image with alpha channel', function(done) { + expect(loadImage(transparentImage, defaultOptions) .then(function(info) { expect(info.transparent).toBe(false); }), done).toResolve(); }); + + it('loads image with checkTransparency flag', function(done) { + var options = clone(defaultOptions); + options.checkTransparency = true; + + expect(loadImage(transparentImage, options) + .then(function(info) { + expect(info.transparent).toBe(true); + }), done).toResolve(); + }); }); diff --git a/specs/lib/mtlSpec.js b/specs/lib/mtlSpec.js index 0be943c..3673d7c 100644 --- a/specs/lib/mtlSpec.js +++ b/specs/lib/mtlSpec.js @@ -1,13 +1,12 @@ 'use strict'; var path = require('path'); -var loadMtl = require('../../lib/mtl.js'); +var loadMtl = require('../../lib/mtl'); var complexMaterialUrl = 'specs/data/box-complex-material/box-complex-material.mtl'; var multipleMaterialsUrl = 'specs/data/box-multiple-materials/box-multiple-materials.mtl'; -var invalidMaterialUrl = 'invalid.mtl'; function getImagePath(objPath, relativePath) { - return path.normalize(path.join(path.dirname(objPath), relativePath)); + return path.resolve(path.dirname(objPath), relativePath); } describe('mtl', function() { @@ -22,10 +21,10 @@ describe('mtl', function() { expect(material.specularColor).toEqual([0.5, 0.5, 0.5, 1.0]); expect(material.specularShininess).toEqual(96.078431); expect(material.alpha).toEqual(0.9); - expect(material.ambientColorMap).toEqual(getImagePath(complexMaterialUrl, 'ambient.gif')); - expect(material.emissionColorMap).toEqual(getImagePath(complexMaterialUrl, 'emission.jpg')); - expect(material.diffuseColorMap).toEqual(getImagePath(complexMaterialUrl, 'diffuse.png')); - expect(material.specularColorMap).toEqual(getImagePath(complexMaterialUrl, 'specular.jpeg')); + expect(material.ambientTexture).toEqual(getImagePath(complexMaterialUrl, 'ambient.gif')); + expect(material.emissionTexture).toEqual(getImagePath(complexMaterialUrl, 'emission.jpg')); + expect(material.diffuseTexture).toEqual(getImagePath(complexMaterialUrl, 'diffuse.png')); + expect(material.specularTexture).toEqual(getImagePath(complexMaterialUrl, 'specular.jpeg')); expect(material.specularShininessMap).toEqual(getImagePath(complexMaterialUrl, 'shininess.png')); expect(material.normalMap).toEqual(getImagePath(complexMaterialUrl, 'bump.png')); expect(material.alphaMap).toEqual(getImagePath(complexMaterialUrl, 'alpha.png')); diff --git a/specs/lib/objSpec.js b/specs/lib/objSpec.js index c1aeac3..e8ac642 100644 --- a/specs/lib/objSpec.js +++ b/specs/lib/objSpec.js @@ -2,8 +2,10 @@ var Cesium = require('cesium'); var path = require('path'); var Promise = require('bluebird'); -var loadObj = require('../../lib/obj.js'); +var convert = require('../../lib/convert'); +var loadObj = require('../../lib/obj'); +var clone = Cesium.clone; var RuntimeError = Cesium.RuntimeError; var objUrl = 'specs/data/box/box.obj'; @@ -54,12 +56,14 @@ function getPrimitives(data) { } function getImagePath(objPath, relativePath) { - return path.normalize(path.join(path.dirname(objPath), relativePath)); + return path.resolve(path.dirname(objPath), relativePath); } +var defaultOptions = convert.defaults; + describe('obj', function() { it('loads obj with positions, normals, and uvs', function(done) { - expect(loadObj(objUrl) + expect(loadObj(objUrl, defaultOptions) .then(function(data) { var images = data.images; var materials = data.materials; @@ -88,7 +92,7 @@ describe('obj', function() { }); it('loads obj with normals', function(done) { - expect(loadObj(objNormalsUrl) + expect(loadObj(objNormalsUrl, defaultOptions) .then(function(data) { var mesh = getMeshes(data)[0]; expect(mesh.positions.length / 3).toBe(24); @@ -98,7 +102,7 @@ describe('obj', function() { }); it('loads obj with uvs', function(done) { - expect(loadObj(objUvsUrl) + expect(loadObj(objUvsUrl, defaultOptions) .then(function(data) { var mesh = getMeshes(data)[0]; expect(mesh.positions.length / 3).toBe(20); @@ -109,8 +113,8 @@ describe('obj', function() { it('loads obj with negative indices', function(done) { expect(Promise.all([ - loadObj(objPositionsOnlyUrl), - loadObj(objNegativeIndicesUrl) + loadObj(objPositionsOnlyUrl, defaultOptions), + loadObj(objNegativeIndicesUrl, defaultOptions) ]) .then(function(results) { var positionsReference = getMeshes(results[0])[0].positions.toFloatBuffer(); @@ -120,7 +124,7 @@ describe('obj', function() { }); it('loads obj with triangle faces', function(done) { - expect(loadObj(objTrianglesUrl) + expect(loadObj(objTrianglesUrl, defaultOptions) .then(function(data) { var mesh = getMeshes(data)[0]; var primitive = getPrimitives(data)[0]; @@ -130,7 +134,7 @@ describe('obj', function() { }); it('loads obj with objects', function(done) { - expect(loadObj(objObjectsUrl) + expect(loadObj(objObjectsUrl, defaultOptions) .then(function(data) { var nodes = data.nodes; expect(nodes.length).toBe(3); @@ -147,7 +151,7 @@ describe('obj', function() { }); it('loads obj with groups', function(done) { - expect(loadObj(objGroupsUrl) + expect(loadObj(objGroupsUrl, defaultOptions) .then(function(data) { var nodes = data.nodes; expect(nodes.length).toBe(3); @@ -164,7 +168,7 @@ describe('obj', function() { }); it('loads obj with objects and groups', function(done) { - expect(loadObj(objObjectsGroupsUrl) + expect(loadObj(objObjectsGroupsUrl, defaultOptions) .then(function(data) { var nodes = data.nodes; expect(nodes.length).toBe(3); @@ -187,7 +191,7 @@ describe('obj', function() { }); it('loads obj with usemtl only', function(done) { - expect(loadObj(objUsemtlUrl) + expect(loadObj(objUsemtlUrl, defaultOptions) .then(function(data) { var nodes = data.nodes; expect(nodes.length).toBe(1); @@ -206,7 +210,7 @@ describe('obj', function() { }); it('loads obj with no materials', function(done) { - expect(loadObj(objNoMaterialsUrl) + expect(loadObj(objNoMaterialsUrl, defaultOptions) .then(function(data) { var nodes = data.nodes; expect(nodes.length).toBe(1); @@ -219,7 +223,7 @@ describe('obj', function() { it('loads obj with multiple materials', function(done) { // The usemtl markers are interleaved, but should condense to just three primitives - expect(loadObj(objMultipleMaterialsUrl) + expect(loadObj(objMultipleMaterialsUrl, defaultOptions) .then(function(data) { var nodes = data.nodes; expect(nodes.length).toBe(1); @@ -239,7 +243,7 @@ describe('obj', function() { it('loads obj uncleaned', function(done) { // Obj with extraneous o, g, and usemtl lines // Also tests handling of o and g lines with the same names - expect(loadObj(objUncleanedUrl) + expect(loadObj(objUncleanedUrl, defaultOptions) .then(function(data) { var nodes = data.nodes; var meshes = getMeshes(data); @@ -255,7 +259,7 @@ describe('obj', function() { }); it('loads obj with multiple mtllibs', function(done) { - expect(loadObj(objMtllibUrl) + expect(loadObj(objMtllibUrl, defaultOptions) .then(function(data) { var materials = data.materials; expect(Object.keys(materials).length).toBe(3); @@ -267,7 +271,7 @@ describe('obj', function() { it('loads obj with missing mtllib', function(done) { spyOn(console, 'log'); - expect(loadObj(objMissingMtllibUrl) + expect(loadObj(objMissingMtllibUrl, defaultOptions) .then(function(data) { expect(data.materials).toEqual({}); expect(console.log.calls.argsFor(0)[0].indexOf('Could not read mtl file') >= 0).toBe(true); @@ -275,19 +279,20 @@ describe('obj', function() { }); it('loads resources outside of the obj directory', function(done) { - expect(loadObj(objExternalResourcesUrl) + expect(loadObj(objExternalResourcesUrl, defaultOptions) .then(function(data) { var imagePath = getImagePath(objTexturedUrl, 'cesium.png'); expect(data.images[imagePath]).toBeDefined(); - expect(data.materials.MaterialTextured.diffuseColorMap).toEqual(imagePath); + expect(data.materials.MaterialTextured.diffuseTexture).toEqual(imagePath); }), done).toResolve(); }); it('does not load resources outside of the obj directory when secure is true', function(done) { spyOn(console, 'log'); - var options = { - secure : true - }; + + var options = clone(defaultOptions); + options.secure = true; + expect(loadObj(objExternalResourcesUrl, options) .then(function(data) { var imagePath = getImagePath(objMissingTextureUrl, 'cesium.png'); @@ -300,36 +305,36 @@ describe('obj', function() { }); it('loads obj with texture', function(done) { - expect(loadObj(objTexturedUrl) + expect(loadObj(objTexturedUrl, defaultOptions) .then(function(data) { var imagePath = getImagePath(objTexturedUrl, 'cesium.png'); expect(data.images[imagePath]).toBeDefined(); - expect(data.materials.Material.diffuseColorMap).toEqual(imagePath); + expect(data.materials.Material.diffuseTexture).toEqual(imagePath); }), done).toResolve(); }); it('loads obj with missing texture', function(done) { spyOn(console, 'log'); - expect(loadObj(objMissingTextureUrl) + expect(loadObj(objMissingTextureUrl, defaultOptions) .then(function(data) { var imagePath = getImagePath(objMissingTextureUrl, 'cesium.png'); expect(data.images[imagePath]).toBeUndefined(); - expect(data.materials.Material.diffuseColorMap).toEqual(imagePath); + expect(data.materials.Material.diffuseTexture).toEqual(imagePath); expect(console.log.calls.argsFor(0)[0].indexOf('Could not read image file') >= 0).toBe(true); }), done).toResolve(); }); it('loads obj with subdirectories', function(done) { - expect(loadObj(objSubdirectoriesUrl) + expect(loadObj(objSubdirectoriesUrl, defaultOptions) .then(function(data) { var imagePath = getImagePath(objSubdirectoriesUrl, path.join('materials', 'images', 'cesium.png')); expect(data.images[imagePath]).toBeDefined(); - expect(data.materials.Material.diffuseColorMap).toEqual(imagePath); + expect(data.materials.Material.diffuseTexture).toEqual(imagePath); }), done).toResolve(); }); it('loads obj with complex material', function(done) { - expect(loadObj(objComplexMaterialUrl) + expect(loadObj(objComplexMaterialUrl, defaultOptions) .then(function(data) { var images = data.images; expect(Object.keys(images).length).toBe(4); // Only ambient, diffuse, emission, and specular maps are supported by the converter @@ -337,10 +342,10 @@ describe('obj', function() { }); it('does not process file with invalid contents', function(done) { - expect(loadObj(objInvalidContentsUrl), done).toRejectWith(RuntimeError); + expect(loadObj(objInvalidContentsUrl, defaultOptions), done).toRejectWith(RuntimeError); }); it('throw when reading invalid file', function(done) { - expect(loadObj(objInvalidUrl), done).toRejectWith(Error); + expect(loadObj(objInvalidUrl, defaultOptions), done).toRejectWith(Error); }); }); From 37febbcd93faef501c6b7e8c0805051f39b67a28 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 12 Apr 2017 16:55:03 -0400 Subject: [PATCH 40/69] Renaming files --- README.md | 6 ++--- bin/obj2gltf.js | 6 ++--- index.js | 2 +- lib/{gltf.js => createGltf.js} | 0 lib/{image.js => loadImage.js} | 0 lib/{mtl.js => loadMtl.js} | 0 lib/{obj.js => loadObj.js} | 4 +-- lib/{convert.js => obj2gltf.js} | 18 ++++++------- package.json | 1 + specs/lib/gltfSpec.js | 10 +++---- specs/lib/imageSpec.js | 6 ++--- specs/lib/mtlSpec.js | 2 +- specs/lib/{convertSpec.js => obj2gltfSpec.js} | 27 +++++++++---------- specs/lib/objSpec.js | 6 ++--- 14 files changed, 44 insertions(+), 44 deletions(-) rename lib/{gltf.js => createGltf.js} (100%) rename lib/{image.js => loadImage.js} (100%) rename lib/{mtl.js => loadMtl.js} (100%) rename lib/{obj.js => loadObj.js} (99%) rename lib/{convert.js => obj2gltf.js} (94%) rename specs/lib/{convertSpec.js => obj2gltfSpec.js} (86%) diff --git a/README.md b/README.md index 0773c26..ebf5cd4 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Using obj2gltf as a command-line tool: ## Usage -###Command line flags: +### Command line flags: |Flag|Description|Required| |----|-----------|--------| @@ -62,7 +62,7 @@ To run JSHint automatically when a file is saved, run the following and leave it npm run jsHint-watch ``` -### Running Test Coverage +## Running Test Coverage Coverage uses [istanbul](https://github.com/gotwarlost/istanbul). Run: ``` @@ -81,7 +81,7 @@ npm run jsdoc The documentation will be placed in the `doc` folder. -### Debugging +## Debugging * To debug the tests in Webstorm, open the Gulp tab, right click the `test` task, and click `Debug 'test'`. * To run a single test, change the test function from `it` to `fit`. diff --git a/bin/obj2gltf.js b/bin/obj2gltf.js index 52e4c0e..8e77725 100644 --- a/bin/obj2gltf.js +++ b/bin/obj2gltf.js @@ -3,11 +3,11 @@ var Cesium = require('cesium'); var path = require('path'); var yargs = require('yargs'); -var convert = require('../lib/convert'); +var obj2gltf = require('../lib/obj2gltf'); var defined = Cesium.defined; -var defaults = convert.defaults; +var defaults = obj2gltf.defaults; var args = process.argv; @@ -124,7 +124,7 @@ var options = { console.time('Total'); -convert(objPath, gltfPath, options) +obj2gltf(objPath, gltfPath, options) .then(function() { console.timeEnd('Total'); }) diff --git a/index.js b/index.js index 02ae37e..f0c4b18 100644 --- a/index.js +++ b/index.js @@ -1 +1 @@ -module.exports = require('./lib/convert'); +module.exports = require('./lib/obj2gltf'); diff --git a/lib/gltf.js b/lib/createGltf.js similarity index 100% rename from lib/gltf.js rename to lib/createGltf.js diff --git a/lib/image.js b/lib/loadImage.js similarity index 100% rename from lib/image.js rename to lib/loadImage.js diff --git a/lib/mtl.js b/lib/loadMtl.js similarity index 100% rename from lib/mtl.js rename to lib/loadMtl.js diff --git a/lib/obj.js b/lib/loadObj.js similarity index 99% rename from lib/obj.js rename to lib/loadObj.js index cfd1d7c..09c8afc 100644 --- a/lib/obj.js +++ b/lib/loadObj.js @@ -4,8 +4,8 @@ var path = require('path'); var Promise = require('bluebird'); var ArrayStorage = require('./ArrayStorage'); -var loadImage = require('./image'); -var loadMtl = require('./mtl'); +var loadImage = require('./loadImage'); +var loadMtl = require('./loadMtl'); var readLines = require('./readLines'); var ComponentDatatype = Cesium.ComponentDatatype; diff --git a/lib/convert.js b/lib/obj2gltf.js similarity index 94% rename from lib/convert.js rename to lib/obj2gltf.js index 5e26300..0326a0e 100644 --- a/lib/convert.js +++ b/lib/obj2gltf.js @@ -4,8 +4,8 @@ var fsExtra = require('fs-extra'); var GltfPipeline = require('gltf-pipeline').Pipeline; var path = require('path'); var Promise = require('bluebird'); -var createGltf = require('./gltf'); -var loadObj = require('./obj'); +var createGltf = require('./createGltf'); +var loadObj = require('./loadObj'); var writeUris = require('./writeUris'); var fsExtraOutputJson = Promise.promisify(fsExtra.outputJson); @@ -14,7 +14,7 @@ var defaultValue = Cesium.defaultValue; var defined = Cesium.defined; var DeveloperError = Cesium.DeveloperError; -module.exports = convert; +module.exports = obj2gltf; /** * Converts an obj file to a glTF file. @@ -37,8 +37,8 @@ module.exports = convert; * @param {Boolean} [options.secure=false] Prevent the converter from reading image or mtl files outside of the input obj directory. * @param {Logger} [options.logger] A callback function for handling logged messages. Defaults to console.log. */ -function convert(objPath, gltfPath, options) { - var defaults = convert.defaults; +function obj2gltf(objPath, gltfPath, options) { + var defaults = obj2gltf.defaults; options = defaultValue(options, {}); var binary = defaultValue(options.binary, defaults.binary); @@ -113,7 +113,7 @@ function convert(objPath, gltfPath, options) { }) .then(function(gltf) { if (bypassPipeline) { - return convert._outputJson(gltfPath, gltf); + return obj2gltf._outputJson(gltfPath, gltf); } else { return GltfPipeline.processJSONToDisk(gltf, gltfPath, pipelineOptions); } @@ -121,9 +121,9 @@ function convert(objPath, gltfPath, options) { } /** - * Default values that will be used when calling convert(options) unless specified in the options object. + * Default values that will be used when calling obj2gltf(options) unless specified in the options object. */ -convert.defaults = { +obj2gltf.defaults = { /** * Gets or sets whether the model will be saved as binary glTF. * @type Boolean @@ -210,7 +210,7 @@ convert.defaults = { * * @private */ -convert._outputJson = fsExtraOutputJson; +obj2gltf._outputJson = fsExtraOutputJson; /** * A callback function that logs messages. diff --git a/package.json b/package.json index 4ea0e01..b15b7d1 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "istanbul": "^0.4.5", "jasmine": "^2.5.3", "jasmine-spec-reporter": "^3.2.0", + "jsdoc": "^3.4.3", "jshint": "^2.9.4", "jshint-stylish": "^2.2.1", "open": "^0.0.5", diff --git a/specs/lib/gltfSpec.js b/specs/lib/gltfSpec.js index b139ada..84d3692 100644 --- a/specs/lib/gltfSpec.js +++ b/specs/lib/gltfSpec.js @@ -3,10 +3,10 @@ var Cesium = require('cesium'); var fsExtra = require('fs-extra'); var path = require('path'); var Promise = require('bluebird'); -var convert = require('../../lib/convert'); -var createGltf = require('../../lib/gltf'); -var loadImage = require('../../lib/image'); -var loadObj = require('../../lib/obj'); +var obj2gltf = require('../../lib/obj2gltf'); +var createGltf = require('../../lib/createGltf'); +var loadImage = require('../../lib/loadImage'); +var loadObj = require('../../lib/loadObj'); var Material = require('../../lib/Material'); var writeUris = require('../../lib/writeUris'); @@ -22,7 +22,7 @@ var groupGltfUrl = 'specs/data/box-objects-groups-materials/box-objects-groups-m var diffuseTextureUrl = 'specs/data/box-textured/cesium.png'; var transparentDiffuseTextureUrl = 'specs/data/box-complex-material/diffuse.png'; -var defaultOptions = convert.defaults; +var defaultOptions = obj2gltf.defaults; var checkTransparencyOptions = clone(defaultOptions); checkTransparencyOptions.checkTransparency = true; diff --git a/specs/lib/imageSpec.js b/specs/lib/imageSpec.js index 452a8c0..bb55745 100644 --- a/specs/lib/imageSpec.js +++ b/specs/lib/imageSpec.js @@ -1,7 +1,7 @@ 'use strict'; var Cesium = require('cesium'); -var convert = require('../../lib/convert'); -var loadImage = require('../../lib/image'); +var obj2gltf = require('../../lib/obj2gltf'); +var loadImage = require('../../lib/loadImage'); var clone = Cesium.clone; var WebGLConstants = Cesium.WebGLConstants; @@ -13,7 +13,7 @@ var gifImage = 'specs/data/box-complex-material/ambient.gif'; var grayscaleImage = 'specs/data/box-complex-material/alpha.png'; var transparentImage = 'specs/data/box-complex-material/diffuse.png'; -var defaultOptions = convert.defaults; +var defaultOptions = obj2gltf.defaults; describe('image', function() { it('loads png image', function(done) { diff --git a/specs/lib/mtlSpec.js b/specs/lib/mtlSpec.js index 3673d7c..e0e112d 100644 --- a/specs/lib/mtlSpec.js +++ b/specs/lib/mtlSpec.js @@ -1,6 +1,6 @@ 'use strict'; var path = require('path'); -var loadMtl = require('../../lib/mtl'); +var loadMtl = require('../../lib/loadMtl'); var complexMaterialUrl = 'specs/data/box-complex-material/box-complex-material.mtl'; var multipleMaterialsUrl = 'specs/data/box-multiple-materials/box-multiple-materials.mtl'; diff --git a/specs/lib/convertSpec.js b/specs/lib/obj2gltfSpec.js similarity index 86% rename from specs/lib/convertSpec.js rename to specs/lib/obj2gltfSpec.js index 2d0476e..b152f9f 100644 --- a/specs/lib/convertSpec.js +++ b/specs/lib/obj2gltfSpec.js @@ -1,19 +1,18 @@ 'use strict'; var GltfPipeline = require('gltf-pipeline').Pipeline; var path = require('path'); -var convert = require('../../lib/convert'); +var obj2gltf = require('../../lib/obj2gltf'); var writeUris = require('../../lib/writeUris'); var objPath = 'specs/data/box-textured/box-textured.obj'; var gltfPath = 'specs/data/box-textured/box-textured.gltf'; var glbPath = 'specs/data/box-textured/box-textured.glb'; var objPathNonExistent = 'specs/data/non-existent.obj'; -var objExternalResourcesPath = 'specs/data/box-external-resources/box-external-resources.obj'; -describe('convert', function() { +describe('obj2gltf', function() { it('converts an obj to gltf', function(done) { var spy = spyOn(GltfPipeline, 'processJSONToDisk'); - expect(convert(objPath, gltfPath) + expect(obj2gltf(objPath, gltfPath) .then(function() { var args = spy.calls.first().args; var gltf = args[0]; @@ -26,7 +25,7 @@ describe('convert', function() { it('uses default gltf-pipeline options', function(done) { var spy = spyOn(GltfPipeline, 'processJSONToDisk'); - expect(convert(objPath, gltfPath) + expect(obj2gltf(objPath, gltfPath) .then(function() { var args = spy.calls.first().args; var options = args[2]; @@ -69,10 +68,10 @@ describe('convert', function() { textureCompressionOptions : textureCompressionOptions, checkTransparency : true, secure : true, - logger : convert.defaults.logger + logger : obj2gltf.defaults.logger }; - expect(convert(objPath, gltfPath, options) + expect(obj2gltf(objPath, gltfPath, options) .then(function() { var args = spy.calls.first().args; var options = args[2]; @@ -98,7 +97,7 @@ describe('convert', function() { it('saves as binary if gltfPath has a .glb extension', function(done) { var spy = spyOn(GltfPipeline, 'processJSONToDisk'); - expect(convert(objPath, glbPath) + expect(obj2gltf(objPath, glbPath) .then(function() { var args = spy.calls.first().args; var options = args[2]; @@ -107,31 +106,31 @@ describe('convert', function() { }); it('bypassPipeline flag bypasses gltf-pipeline', function(done) { - spyOn(convert, '_outputJson'); + spyOn(obj2gltf, '_outputJson'); spyOn(GltfPipeline, 'processJSONToDisk'); var options = { bypassPipeline : true }; - expect(convert(objPath, gltfPath, options) + expect(obj2gltf(objPath, gltfPath, options) .then(function() { - expect(convert._outputJson).toHaveBeenCalled(); + expect(obj2gltf._outputJson).toHaveBeenCalled(); expect(GltfPipeline.processJSONToDisk).not.toHaveBeenCalled(); }), done).toResolve(); }); it('rejects if obj path does not exist', function(done) { - expect(convert(objPathNonExistent, gltfPath), done).toRejectWith(Error); + expect(obj2gltf(objPathNonExistent, gltfPath), done).toRejectWith(Error); }); it('throws if objPath is undefined', function() { expect(function() { - convert(undefined, gltfPath); + obj2gltf(undefined, gltfPath); }).toThrowDeveloperError(); }); it('rejects if gltfPath is undefined', function() { expect(function() { - convert(objPath, undefined); + obj2gltf(objPath, undefined); }).toThrowDeveloperError(); }); }); diff --git a/specs/lib/objSpec.js b/specs/lib/objSpec.js index e8ac642..7535c83 100644 --- a/specs/lib/objSpec.js +++ b/specs/lib/objSpec.js @@ -2,8 +2,8 @@ var Cesium = require('cesium'); var path = require('path'); var Promise = require('bluebird'); -var convert = require('../../lib/convert'); -var loadObj = require('../../lib/obj'); +var loadObj = require('../../lib/loadObj'); +var obj2gltf = require('../../lib/obj2gltf'); var clone = Cesium.clone; var RuntimeError = Cesium.RuntimeError; @@ -59,7 +59,7 @@ function getImagePath(objPath, relativePath) { return path.resolve(path.dirname(objPath), relativePath); } -var defaultOptions = convert.defaults; +var defaultOptions = obj2gltf.defaults; describe('obj', function() { it('loads obj with positions, normals, and uvs', function(done) { From fb28eed9cd9d7f3de7fc166099d5c20d8b9443ff Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 13 Apr 2017 10:13:03 -0400 Subject: [PATCH 41/69] Add Webstorm project file --- .idea/jsLibraryMappings.xml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .idea/jsLibraryMappings.xml diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml new file mode 100644 index 0000000..d23208f --- /dev/null +++ b/.idea/jsLibraryMappings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From 212aed8e8a9ba4023c14c9523d4489979f0c593d Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 13 Apr 2017 10:28:20 -0400 Subject: [PATCH 42/69] Update package.json and CHANGES.md --- CHANGES.md | 11 +++++++++++ package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index b0db92d..326fa8c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,17 @@ Change Log ========== +### 1.0.0 2017-04-13 + +* 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)`. + * Many library options and command-line parameters have been renamed. +* Project cleanup. [#49](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/49) + * Speed improvements, especially for larger models. + * Preserves the objects and groups in the obj. + * Added documentation and tests. + * Material fixes. + ### 0.1.7 2017-01-06 * Update gltf-pipeline to 0.1.0-alpha9 diff --git a/package.json b/package.json index b15b7d1..74fd41f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obj2gltf", - "version": "0.1.7", + "version": "1.0.0", "description": "Convert OBJ model format to glTF", "license": "Apache-2.0", "contributors": [ From 45feac8dd00a758d39e686f94b4dac0abdfea7a9 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 13 Apr 2017 10:29:56 -0400 Subject: [PATCH 43/69] Fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ebf5cd4..412c69a 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Using obj2gltf as a command-line tool: |`-n`, `--generateNormals`|Generate normals if they are missing.|No, default `false`| |`--optimizeForCesium`|Optimize the glTF for Cesium by using the sun as a default light source.|No, default `false`| |`--ao`|Apply ambient occlusion to the converted model.|No, default `false`| -|`--kmc|Output glTF with the KHR_materials_common extension.|No, default `false`| +|`--kmc`|Output glTF with the KHR_materials_common extension.|No, default `false`| |`--bypassPipeline`|Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension.|No, default `false`| |`--checkTransparency`|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.|No, default `false`| |`--secure`|Prevent the converter from reading image or mtl files outside of the input obj directory.|No, default `false`| From a6ff230fc3f92c01b5d04ca9940b8b1facfc4e78 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 14 Apr 2017 12:00:43 -0400 Subject: [PATCH 44/69] Don't save out two bins --- lib/obj2gltf.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/obj2gltf.js b/lib/obj2gltf.js index 0326a0e..d6c74fc 100644 --- a/lib/obj2gltf.js +++ b/lib/obj2gltf.js @@ -9,6 +9,7 @@ var loadObj = require('./loadObj'); var writeUris = require('./writeUris'); var fsExtraOutputJson = Promise.promisify(fsExtra.outputJson); +var fsExtraRemove = Promise.promisify(fsExtra.remove); var defaultValue = Cesium.defaultValue; var defined = Cesium.defined; @@ -117,9 +118,22 @@ function obj2gltf(objPath, gltfPath, options) { } else { return GltfPipeline.processJSONToDisk(gltf, gltfPath, pipelineOptions); } + }) + .then(function() { + return cleanup(gltfPath, options); }); } +function cleanup(gltfPath, options) { + // gltf-pipeline also saves out a buffer so remove the one generated by obj2gltf + if (!options.bypassPipeline && options.separate) { + var bufferName = path.basename(gltfPath, path.extname(gltfPath)); + var bufferUri = bufferName + '.bin'; + var bufferPath = path.join(path.dirname(gltfPath), bufferUri); + return fsExtraRemove(bufferPath); + } +} + /** * Default values that will be used when calling obj2gltf(options) unless specified in the options object. */ From 63a51784411bfa1dddb3b5c7f1d64a5c3d26399f Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 19 Apr 2017 15:48:07 -0400 Subject: [PATCH 45/69] Fixed spec names --- specs/lib/{gltfSpec.js => createGltfSpec.js} | 2 +- specs/lib/{imageSpec.js => loadImageSpec.js} | 2 +- specs/lib/{mtlSpec.js => loadMtlSpec.js} | 2 +- specs/lib/{objSpec.js => loadObjSpec.js} | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename specs/lib/{gltfSpec.js => createGltfSpec.js} (99%) rename specs/lib/{imageSpec.js => loadImageSpec.js} (98%) rename specs/lib/{mtlSpec.js => loadMtlSpec.js} (98%) rename specs/lib/{objSpec.js => loadObjSpec.js} (99%) diff --git a/specs/lib/gltfSpec.js b/specs/lib/createGltfSpec.js similarity index 99% rename from specs/lib/gltfSpec.js rename to specs/lib/createGltfSpec.js index 84d3692..c509492 100644 --- a/specs/lib/gltfSpec.js +++ b/specs/lib/createGltfSpec.js @@ -26,7 +26,7 @@ var defaultOptions = obj2gltf.defaults; var checkTransparencyOptions = clone(defaultOptions); checkTransparencyOptions.checkTransparency = true; -describe('gltf', function() { +describe('createGltf', function() { var boxObjData; var duplicateBoxObjData; var groupObjData; diff --git a/specs/lib/imageSpec.js b/specs/lib/loadImageSpec.js similarity index 98% rename from specs/lib/imageSpec.js rename to specs/lib/loadImageSpec.js index bb55745..3554d83 100644 --- a/specs/lib/imageSpec.js +++ b/specs/lib/loadImageSpec.js @@ -15,7 +15,7 @@ var transparentImage = 'specs/data/box-complex-material/diffuse.png'; var defaultOptions = obj2gltf.defaults; -describe('image', function() { +describe('loadImage', function() { it('loads png image', function(done) { expect(loadImage(pngImage, defaultOptions) .then(function(info) { diff --git a/specs/lib/mtlSpec.js b/specs/lib/loadMtlSpec.js similarity index 98% rename from specs/lib/mtlSpec.js rename to specs/lib/loadMtlSpec.js index e0e112d..976b322 100644 --- a/specs/lib/mtlSpec.js +++ b/specs/lib/loadMtlSpec.js @@ -9,7 +9,7 @@ function getImagePath(objPath, relativePath) { return path.resolve(path.dirname(objPath), relativePath); } -describe('mtl', function() { +describe('loadMtl', function() { it('loads complex material', function(done) { expect(loadMtl(complexMaterialUrl) .then(function(materials) { diff --git a/specs/lib/objSpec.js b/specs/lib/loadObjSpec.js similarity index 99% rename from specs/lib/objSpec.js rename to specs/lib/loadObjSpec.js index 7535c83..3375e64 100644 --- a/specs/lib/objSpec.js +++ b/specs/lib/loadObjSpec.js @@ -61,7 +61,7 @@ function getImagePath(objPath, relativePath) { var defaultOptions = obj2gltf.defaults; -describe('obj', function() { +describe('loadObj', function() { it('loads obj with positions, normals, and uvs', function(done) { expect(loadObj(objUrl, defaultOptions) .then(function(data) { From d7a354b313930ce1e8869665a047bf967fcfc532 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 20 Apr 2017 10:07:01 -0400 Subject: [PATCH 46/69] Use temp directory --- lib/obj2gltf.js | 24 ++++++++++++++---------- lib/writeUris.js | 22 ++++++++++++---------- package.json | 1 + 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/lib/obj2gltf.js b/lib/obj2gltf.js index d6c74fc..3d4386b 100644 --- a/lib/obj2gltf.js +++ b/lib/obj2gltf.js @@ -2,8 +2,10 @@ var Cesium = require('cesium'); var fsExtra = require('fs-extra'); var GltfPipeline = require('gltf-pipeline').Pipeline; +var os = require('os'); var path = require('path'); var Promise = require('bluebird'); +var uuid = require('uuid'); var createGltf = require('./createGltf'); var loadObj = require('./loadObj'); var writeUris = require('./writeUris'); @@ -73,7 +75,6 @@ function obj2gltf(objPath, gltfPath, options) { } var extension = path.extname(gltfPath).toLowerCase(); - var basePath = path.dirname(gltfPath); var modelName = path.basename(gltfPath, path.extname(gltfPath)); if (extension === '.glb') { binary = true; @@ -84,13 +85,14 @@ function obj2gltf(objPath, gltfPath, options) { } gltfPath = path.join(path.dirname(gltfPath), modelName + extension); + var resourcesDirectory = options.bypassPipeline ? path.dirname(gltfPath) : getTempDirectory(); var aoOptions = ao ? {} : undefined; var kmcOptions = kmc ? {} : undefined; var pipelineOptions = { createDirectory : false, - basePath : basePath, + basePath : resourcesDirectory, binary : binary, embed : !separate, embedImage : !separateTextures, @@ -110,7 +112,7 @@ function obj2gltf(objPath, gltfPath, options) { return createGltf(objData); }) .then(function(gltf) { - return writeUris(gltf, gltfPath, options); + return writeUris(gltf, gltfPath, resourcesDirectory, options); }) .then(function(gltf) { if (bypassPipeline) { @@ -120,20 +122,22 @@ function obj2gltf(objPath, gltfPath, options) { } }) .then(function() { - return cleanup(gltfPath, options); + return cleanup(resourcesDirectory, options); }); } -function cleanup(gltfPath, options) { - // gltf-pipeline also saves out a buffer so remove the one generated by obj2gltf +function cleanup(resourcesDirectory, options) { if (!options.bypassPipeline && options.separate) { - var bufferName = path.basename(gltfPath, path.extname(gltfPath)); - var bufferUri = bufferName + '.bin'; - var bufferPath = path.join(path.dirname(gltfPath), bufferUri); - return fsExtraRemove(bufferPath); + return fsExtraRemove(resourcesDirectory); } } +function getTempDirectory() { + var tempDirectory = os.tmpdir(); + var randomId = uuid.v4(); + return path.join(tempDirectory, randomId); +} + /** * Default values that will be used when calling obj2gltf(options) unless specified in the options object. */ diff --git a/lib/writeUris.js b/lib/writeUris.js index 53e23c3..1a578fd 100644 --- a/lib/writeUris.js +++ b/lib/writeUris.js @@ -16,6 +16,7 @@ module.exports = writeUris; * * @param {Object} gltf The glTF asset. * @param {String} gltfPath Path where the glTF will be saved. + * @param {String} resourcesDirectory Path where separate resources will be saved. * @param {Object} options An object with the following properties: * @param {Boolean} options.separate Writes out separate buffers. * @param {Boolean} options.separateTextures Write out separate textures only. @@ -23,7 +24,7 @@ module.exports = writeUris; * * @private */ -function writeUris(gltf, gltfPath, options) { +function writeUris(gltf, gltfPath, resourcesDirectory, options) { var separate = options.separate; var separateTextures = options.separateTextures; @@ -44,17 +45,19 @@ function writeUris(gltf, gltfPath, options) { var exceedsMaximum = (texturesByteLength + bufferByteLength > 201326580); if (exceedsMaximum && !separate) { - return Promise.reject(new RuntimeError('Buffers and textures are too large to encode in the glTF, saving as separate resources.')); + return Promise.reject(new RuntimeError('Buffers and textures are too large to encode in the glTF. Use the --separate flag instead.')); } + var name = path.basename(gltfPath, path.extname(gltfPath)); + if (separate) { - promises.push(writeSeparateBuffer(gltf, gltfPath)); + promises.push(writeSeparateBuffer(gltf, resourcesDirectory, name)); } else { writeEmbeddedBuffer(gltf); } if (separateTextures) { - promises.push(writeSeparateTextures(gltf, gltfPath)); + promises.push(writeSeparateTextures(gltf, resourcesDirectory)); } else { writeEmbeddedTextures(gltf); } @@ -79,24 +82,23 @@ function deleteExtras(gltf) { } } -function writeSeparateBuffer(gltf, gltfPath) { +function writeSeparateBuffer(gltf, resourcesDirectory, name) { var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; var source = buffer.extras._obj2gltf.source; - var bufferName = path.basename(gltfPath, path.extname(gltfPath)); - var bufferUri = bufferName + '.bin'; + var bufferUri = name + '.bin'; buffer.uri = bufferUri; - var bufferPath = path.join(path.dirname(gltfPath), bufferUri); + var bufferPath = path.join(resourcesDirectory, bufferUri); return writeUris._outputFile(bufferPath, source); } -function writeSeparateTextures(gltf, gltfPath) { +function writeSeparateTextures(gltf, resourcesDirectory) { var images = gltf.images; return Promise.map(Object.keys(images), function(id) { var image = images[id]; var extras = image.extras._obj2gltf; var imageUri = image.name + extras.extension; image.uri = imageUri; - var imagePath = path.join(path.dirname(gltfPath), imageUri); + var imagePath = path.join(resourcesDirectory, imageUri); return writeUris._outputFile(imagePath, extras.source); }, {concurrency : 10}); } diff --git a/package.json b/package.json index 74fd41f..6a45bfd 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "gltf-pipeline": "^0.1.0-alpha11", "mime": "^1.3.4", "pngjs": "^3.0.1", + "uuid": "^3.0.1", "yargs": "^7.0.1" }, "devDependencies": { From 93dac5ebdc515982a01a833bc234606e5fbb7308 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 20 Apr 2017 14:41:39 -0400 Subject: [PATCH 47/69] Convert up axis --- README.md | 2 + bin/obj2gltf.js | 16 +++- lib/loadObj.js | 51 +++++++++-- lib/obj2gltf.js | 19 ++++- specs/data/box-rotated/box-rotated.mtl | 10 +++ specs/data/box-rotated/box-rotated.obj | 112 +++++++++++++++++++++++++ specs/lib/loadObjSpec.js | 51 ++++++++++- specs/lib/obj2gltfSpec.js | 2 + 8 files changed, 254 insertions(+), 9 deletions(-) create mode 100644 specs/data/box-rotated/box-rotated.mtl create mode 100644 specs/data/box-rotated/box-rotated.obj diff --git a/README.md b/README.md index 412c69a..565a4bb 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ Using obj2gltf as a command-line tool: |`--bypassPipeline`|Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension.|No, default `false`| |`--checkTransparency`|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.|No, default `false`| |`--secure`|Prevent the converter from reading image or mtl files outside of the input obj directory.|No, default `false`| +|`--inputUpAxis`|Up axis of the obj. Choices are 'X', 'Y', and 'Z'.|No, default `Y`| +|`--outputUpAxis`|Up axis of the converted glTF. Choices are 'X', 'Y', and 'Z'.|No, default `Y`| ## Build Instructions diff --git a/bin/obj2gltf.js b/bin/obj2gltf.js index 8e77725..478664c 100644 --- a/bin/obj2gltf.js +++ b/bin/obj2gltf.js @@ -95,6 +95,18 @@ var argv = yargs describe: 'Prevent the converter from reading image or mtl files outside of the input obj directory.', type: 'boolean', default: defaults.secure + }, + inputUpAxis : { + describe: 'Up axis of the obj.', + choices: ['X', 'Y', 'Z'], + type: 'string', + default: 'Y' + }, + outputUpAxis : { + describe: 'Up axis of the converted glTF.', + choices: ['X', 'Y', 'Z'], + type: 'string', + default: 'Y' } }).parse(args); @@ -119,7 +131,9 @@ var options = { kmc : argv.kmc, bypassPipeline : argv.bypassPipeline, checkTransparency : argv.checkTransparency, - secure : argv.secure + secure : argv.secure, + inputUpAxis : argv.inputUpAxis, + outputUpAxis : argv.outputUpAxis }; console.time('Total'); diff --git a/lib/loadObj.js b/lib/loadObj.js index 09c8afc..148ff31 100644 --- a/lib/loadObj.js +++ b/lib/loadObj.js @@ -8,9 +8,12 @@ var loadImage = require('./loadImage'); var loadMtl = require('./loadMtl'); var readLines = require('./readLines'); +var Axis = Cesium.Axis; +var Cartesian3 = Cesium.Cartesian3; var ComponentDatatype = Cesium.ComponentDatatype; var defaultValue = Cesium.defaultValue; var defined = Cesium.defined; +var Matrix4 = Cesium.Matrix4; var RuntimeError = Cesium.RuntimeError; module.exports = loadObj; @@ -46,6 +49,8 @@ var facePattern2 = /f( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/( var facePattern3 = /f( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))?/; // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ... var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/; // f vertex//normal vertex//normal vertex//normal ... +var scratchCartesian = new Cartesian3(); + /** * Parse an obj file. * @@ -53,6 +58,8 @@ var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/( * @param {Object} options An object with the following properties: * @param {Boolean} options.checkTransparency Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. * @param {Boolean} options.secure Prevent the converter from reading image or mtl files outside of the input obj directory. + * @param {String} options.inputUpAxis Up axis of the obj. + * @param {String} options.outputUpAxis Up axis of the converted glTF. * @param {Boolean} options.logger A callback function for handling logged messages. Defaults to console.log. * @returns {Promise} A promise resolving to the obj data. * @exception {RuntimeError} The file does not have any geometry information in it. @@ -60,6 +67,8 @@ var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/( * @private */ function loadObj(objPath, options) { + var axisTransform = getAxisTransform(options.inputUpAxis, options.outputUpAxis); + // Global store of vertex attributes listed in the obj file var positions = new ArrayStorage(ComponentDatatype.FLOAT); var normals = new ArrayStorage(ComponentDatatype.FLOAT); @@ -223,13 +232,27 @@ function loadObj(objPath, options) { var paths = line.substring(7).trim().split(' '); mtlPaths = mtlPaths.concat(paths); } else if ((result = vertexPattern.exec(line)) !== null) { - positions.push(parseFloat(result[1])); - positions.push(parseFloat(result[2])); - positions.push(parseFloat(result[3])); + var position = scratchCartesian; + position.x = parseFloat(result[1]); + position.y = parseFloat(result[2]); + position.z = parseFloat(result[3]); + if (defined(axisTransform)) { + Matrix4.multiplyByPoint(axisTransform, position, position); + } + positions.push(position.x); + positions.push(position.y); + positions.push(position.z); } else if ((result = normalPattern.exec(line) ) !== null) { - normals.push(parseFloat(result[1])); - normals.push(parseFloat(result[2])); - normals.push(parseFloat(result[3])); + var normal = scratchCartesian; + normal.x = parseFloat(result[1]); + normal.y = parseFloat(result[2]); + normal.z = parseFloat(result[3]); + if (defined(axisTransform)) { + Matrix4.multiplyByPointAsVector(axisTransform, normal, normal); + } + normals.push(normal.x); + normals.push(normal.y); + normals.push(normal.z); } else if ((result = uvPattern.exec(line)) !== null) { uvs.push(parseFloat(result[1])); uvs.push(1.0 - parseFloat(result[2])); // Flip y so 0.0 is the bottom of the image @@ -446,3 +469,19 @@ function cleanNodes(nodes) { setDefaults(nodes); return nodes; } + +function getAxisTransform(inputUpAxis, outputUpAxis) { + if (inputUpAxis === 'X' && outputUpAxis === 'Y') { + return Axis.X_UP_TO_Y_UP; + } else if (inputUpAxis === 'X' && outputUpAxis === 'Z') { + return Axis.X_UP_TO_Z_UP; + } else if (inputUpAxis === 'Y' && outputUpAxis === 'X') { + return Axis.Y_UP_TO_X_UP; + } else if (inputUpAxis === 'Y' && outputUpAxis === 'Z') { + return Axis.Y_UP_TO_Z_UP; + } else if (inputUpAxis === 'Z' && outputUpAxis === 'X') { + return Axis.Z_UP_TO_X_UP; + } else if (inputUpAxis === 'Z' && outputUpAxis === 'Y') { + return Axis.Z_UP_TO_Y_UP; + } +} diff --git a/lib/obj2gltf.js b/lib/obj2gltf.js index 0326a0e..f1863bf 100644 --- a/lib/obj2gltf.js +++ b/lib/obj2gltf.js @@ -35,6 +35,8 @@ module.exports = obj2gltf; * @param {Boolean} [options.bypassPipeline=false] Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension. * @param {Boolean} [options.checkTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. * @param {Boolean} [options.secure=false] Prevent the converter from reading image or mtl files outside of the input obj directory. + * @param {String} [options.inputUpAxis='Y'] Up axis of the obj. Choices are 'X', 'Y', and 'Z'. + * @param {String} [options.outputUpAxis='Z'] Up axis of the converted glTF. Choices are 'X', 'Y', and 'Z'. * @param {Logger} [options.logger] A callback function for handling logged messages. Defaults to console.log. */ function obj2gltf(objPath, gltfPath, options) { @@ -54,15 +56,18 @@ function obj2gltf(objPath, gltfPath, options) { var bypassPipeline = defaultValue(options.bypassPipeline, defaults.bypassPipeline); var checkTransparency = defaultValue(options.checkTransparency, defaults.checkTransparency); var secure = defaultValue(options.secure, defaults.secure); + var inputUpAxis = defaultValue(options.inputUpAxis, defaults.inputUpAxis); + var outputUpAxis = defaultValue(options.outputUpAxis, defaults.outputUpAxis); var logger = defaultValue(options.logger, defaults.logger); options.separate = separate; options.separateTextures = separateTextures; options.checkTransparency = checkTransparency; options.secure = secure; + options.inputUpAxis = inputUpAxis; + options.outputUpAxis = outputUpAxis; options.logger = logger; - if (!defined(objPath)) { throw new DeveloperError('objPath is required'); } @@ -197,6 +202,18 @@ obj2gltf.defaults = { * @default false */ secure: false, + /** + * Gets or sets the up axis of the obj. + * @type String + * @default 'Y' + */ + inputUpAxis: 'Y', + /** + * Gets or sets the up axis of the converted glTF. + * @type String + * @default 'Y' + */ + outputUpAxis: 'Y', /** * @private */ diff --git a/specs/data/box-rotated/box-rotated.mtl b/specs/data/box-rotated/box-rotated.mtl new file mode 100644 index 0000000..616ae6b --- /dev/null +++ b/specs/data/box-rotated/box-rotated.mtl @@ -0,0 +1,10 @@ +# Blender MTL File: 'axis.blend' +# Material Count: 1 + +newmtl None +Ns 0 +Ka 0.000000 0.000000 0.000000 +Kd 0.8 0.8 0.8 +Ks 0.8 0.8 0.8 +d 1 +illum 2 diff --git a/specs/data/box-rotated/box-rotated.obj b/specs/data/box-rotated/box-rotated.obj new file mode 100644 index 0000000..89d8c29 --- /dev/null +++ b/specs/data/box-rotated/box-rotated.obj @@ -0,0 +1,112 @@ +# Blender v2.78 (sub 0) OBJ File: 'axis.blend' +# www.blender.org +mtllib box-rotated.mtl +o Cube.002_Cube +v -1.707107 -0.292893 0.000000 +v -0.707107 0.707107 1.414214 +v -0.707107 0.707107 -1.414214 +v 0.292893 1.707107 0.000000 +v -0.292893 -1.707107 0.000000 +v 0.707107 -0.707107 1.414214 +v 0.707107 -0.707107 -1.414214 +v 1.707107 0.292893 0.000000 +vn -0.7071 0.7071 0.0000 +vn 0.5000 0.5000 -0.7071 +vn 0.7071 -0.7071 -0.0000 +vn -0.5000 -0.5000 0.7071 +vn -0.5000 -0.5000 -0.7071 +vn 0.5000 0.5000 0.7071 +usemtl None +s off +f 1//1 2//1 4//1 3//1 +f 3//2 4//2 8//2 7//2 +f 7//3 8//3 6//3 5//3 +f 5//4 6//4 2//4 1//4 +f 3//5 7//5 5//5 1//5 +f 8//6 4//6 2//6 6//6 +o Cube.001_Cube.002 +v -3.997752 -1.000000 3.997752 +v -3.997752 1.000000 3.997752 +v -3.997752 -1.000000 -3.997752 +v -3.997752 1.000000 -3.997752 +v 3.997752 -1.000000 3.997752 +v 3.997752 1.000000 3.997752 +v 3.997752 -1.000000 -3.997752 +v 3.997752 1.000000 -3.997752 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl None +s off +f 9//7 10//7 12//7 11//7 +f 11//8 12//8 16//8 15//8 +f 15//9 16//9 14//9 13//9 +f 13//10 14//10 10//10 9//10 +f 11//11 15//11 13//11 9//11 +f 16//12 12//12 10//12 14//12 +o Cube_Cube.001 +v -1.000000 4.235625 1.000000 +v -1.000000 6.235625 1.000000 +v -1.000000 4.235625 -1.000000 +v -1.000000 6.235625 -1.000000 +v 1.000000 4.235625 1.000000 +v 1.000000 6.235625 1.000000 +v 1.000000 4.235625 -1.000000 +v 1.000000 6.235625 -1.000000 +v 0.000000 9.029284 0.000000 +v 1.000000 4.663946 0.571679 +v 1.000000 5.807305 0.571679 +v 1.000000 4.663946 -0.571679 +v 1.000000 5.807305 -0.571679 +v 2.375958 4.663946 0.571679 +v 2.375958 5.807305 0.571679 +v 2.375958 4.663946 -0.571679 +v 2.375958 5.807305 -0.571679 +v -0.310735 4.924891 -1.000000 +v -0.310735 5.546360 -1.000000 +v 0.310735 4.924891 -1.000000 +v 0.310735 5.546360 -1.000000 +v 0.000000 5.235625 -1.538583 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 0.3370 -0.9415 +vn -0.9415 0.3370 0.0000 +vn 0.0000 0.3370 0.9415 +vn 0.9415 0.3370 0.0000 +vn 0.0000 1.0000 0.0000 +vn 0.8662 0.0000 -0.4997 +vn 0.0000 -0.8662 -0.4997 +vn 0.0000 0.8662 -0.4997 +vn -0.8662 0.0000 -0.4997 +usemtl None +s off +f 17//13 18//13 20//13 19//13 +f 23//14 19//14 34//14 36//14 +f 22//15 21//15 26//15 27//15 +f 21//16 22//16 18//16 17//16 +f 19//17 23//17 21//17 17//17 +f 24//18 20//18 25//18 +f 20//19 18//19 25//19 +f 18//20 22//20 25//20 +f 22//21 24//21 25//21 +f 29//22 27//22 31//22 33//22 +f 21//15 23//15 28//15 26//15 +f 24//15 22//15 27//15 29//15 +f 23//15 24//15 29//15 28//15 +f 32//15 33//15 31//15 30//15 +f 28//14 29//14 33//14 32//14 +f 27//16 26//16 30//16 31//16 +f 26//17 28//17 32//17 30//17 +f 37//23 36//23 38//23 +f 20//14 24//14 37//14 35//14 +f 19//14 20//14 35//14 34//14 +f 24//14 23//14 36//14 37//14 +f 36//24 34//24 38//24 +f 35//25 37//25 38//25 +f 34//26 35//26 38//26 diff --git a/specs/lib/loadObjSpec.js b/specs/lib/loadObjSpec.js index 3375e64..f09cf06 100644 --- a/specs/lib/loadObjSpec.js +++ b/specs/lib/loadObjSpec.js @@ -5,10 +5,12 @@ var Promise = require('bluebird'); var loadObj = require('../../lib/loadObj'); var obj2gltf = require('../../lib/obj2gltf'); +var Cartesian3 = Cesium.Cartesian3; var clone = Cesium.clone; var RuntimeError = Cesium.RuntimeError; var objUrl = 'specs/data/box/box.obj'; +var objRotatedUrl = 'specs/data/box-rotated/box-rotated.obj'; var objNormalsUrl = 'specs/data/box-normals/box-normals.obj'; var objUvsUrl = 'specs/data/box-uvs/box-uvs.obj'; var objPositionsOnlyUrl = 'specs/data/box-positions-only/box-positions-only.obj'; @@ -341,7 +343,54 @@ describe('loadObj', function() { }), done).toResolve(); }); - it('does not process file with invalid contents', function(done) { + function getFirstPosition(data) { + var positions = data.nodes[0].meshes[0].positions; + return new Cartesian3(positions.get(0), positions.get(1), positions.get(2)); + } + + function getFirstNormal(data) { + var normals = data.nodes[0].meshes[0].normals; + return new Cartesian3(normals.get(0), normals.get(1), normals.get(2)); + } + + function checkAxisConversion(inputUpAxis, outputUpAxis, position, normal) { + var sameAxis = (inputUpAxis === outputUpAxis); + var options = clone(defaultOptions); + options.inputUpAxis = inputUpAxis; + options.outputUpAxis = outputUpAxis; + return loadObj(objRotatedUrl, options) + .then(function(data) { + var rotatedPosition = getFirstPosition(data); + var rotatedNormal = getFirstNormal(data); + if (sameAxis) { + expect(rotatedPosition).toEqual(position); + expect(rotatedNormal).toEqual(normal); + } else { + expect(rotatedPosition).not.toEqual(position); + expect(rotatedNormal).not.toEqual(normal); + } + }); + } + + it('performs up axis conversion', function(done) { + expect(loadObj(objRotatedUrl, defaultOptions) + .then(function(data) { + var position = getFirstPosition(data); + var normal = getFirstNormal(data); + + var axes = ['X', 'Y', 'Z']; + var axesLength = axes.length; + var promises = []; + for (var i = 0; i < axesLength; ++i) { + for (var j = 0; j < axesLength; ++j) { + promises.push(checkAxisConversion(axes[i], axes[j], position, normal)); + } + } + return Promise.all(promises); + }), done).toResolve(); + }); + + it('throws when file has invalid contents', function(done) { expect(loadObj(objInvalidContentsUrl, defaultOptions), done).toRejectWith(RuntimeError); }); diff --git a/specs/lib/obj2gltfSpec.js b/specs/lib/obj2gltfSpec.js index b152f9f..793d885 100644 --- a/specs/lib/obj2gltfSpec.js +++ b/specs/lib/obj2gltfSpec.js @@ -68,6 +68,8 @@ describe('obj2gltf', function() { textureCompressionOptions : textureCompressionOptions, checkTransparency : true, secure : true, + inputUpAxis : 'Z', + outputUpAxis : 'X', logger : obj2gltf.defaults.logger }; From 1ccf070a6e305264b97b301a700d61d882f62fca Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 20 Apr 2017 14:55:47 -0400 Subject: [PATCH 48/69] Fix tests --- specs/lib/gltfSpec.js | 4 ++-- specs/lib/obj2gltfSpec.js | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/specs/lib/gltfSpec.js b/specs/lib/gltfSpec.js index 84d3692..00afa61 100644 --- a/specs/lib/gltfSpec.js +++ b/specs/lib/gltfSpec.js @@ -70,7 +70,7 @@ describe('gltf', function() { it('simple gltf', function(done) { var gltf = createGltf(boxObjData); - expect(writeUris(gltf, boxGltfUrl, defaultOptions) + expect(writeUris(gltf, boxGltfUrl, path.dirname(boxGltfUrl), defaultOptions) .then(function() { expect(gltf).toEqual(boxGltf); }), done).toResolve(); @@ -79,7 +79,7 @@ describe('gltf', function() { it('multiple nodes, meshes, and primitives', function(done) { var gltf = createGltf(groupObjData); - expect(writeUris(gltf, groupGltfUrl, defaultOptions) + expect(writeUris(gltf, groupGltfUrl, path.dirname(groupGltfUrl), defaultOptions) .then(function() { expect(gltf).toEqual(groupGltf); expect(Object.keys(gltf.materials).length).toBe(3); diff --git a/specs/lib/obj2gltfSpec.js b/specs/lib/obj2gltfSpec.js index b152f9f..eda21f7 100644 --- a/specs/lib/obj2gltfSpec.js +++ b/specs/lib/obj2gltfSpec.js @@ -29,9 +29,10 @@ describe('obj2gltf', function() { .then(function() { var args = spy.calls.first().args; var options = args[2]; + expect(options.basePath).toBeDefined(); + delete options.basePath; // This will be a random temp directory expect(options).toEqual({ createDirectory : false, - basePath : path.dirname(objPath), binary : false, embed : true, embedImage : true, @@ -75,9 +76,10 @@ describe('obj2gltf', function() { .then(function() { var args = spy.calls.first().args; var options = args[2]; + expect(options.basePath).toBeDefined(); + delete options.basePath; // This will be a random temp directory expect(options).toEqual({ createDirectory : false, - basePath : path.dirname(objPath), binary : true, embed : false, embedImage : false, From ec7acba6a955dec6d2082f7f6aa086e01bee4395 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 20 Apr 2017 15:09:03 -0400 Subject: [PATCH 49/69] Fix doc --- lib/obj2gltf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/obj2gltf.js b/lib/obj2gltf.js index f1863bf..09b3700 100644 --- a/lib/obj2gltf.js +++ b/lib/obj2gltf.js @@ -36,7 +36,7 @@ module.exports = obj2gltf; * @param {Boolean} [options.checkTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. * @param {Boolean} [options.secure=false] Prevent the converter from reading image or mtl files outside of the input obj directory. * @param {String} [options.inputUpAxis='Y'] Up axis of the obj. Choices are 'X', 'Y', and 'Z'. - * @param {String} [options.outputUpAxis='Z'] Up axis of the converted glTF. Choices are 'X', 'Y', and 'Z'. + * @param {String} [options.outputUpAxis='Y'] Up axis of the converted glTF. Choices are 'X', 'Y', and 'Z'. * @param {Logger} [options.logger] A callback function for handling logged messages. Defaults to console.log. */ function obj2gltf(objPath, gltfPath, options) { From 20bbd17cd95bf58d2db49ddd90759901b3255298 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Sun, 23 Apr 2017 13:34:09 -0400 Subject: [PATCH 50/69] Fix ambient of 1,1,1 --- lib/createGltf.js | 7 +++++++ specs/lib/createGltfSpec.js | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/createGltf.js b/lib/createGltf.js index eb6aa7c..33191c8 100644 --- a/lib/createGltf.js +++ b/lib/createGltf.js @@ -86,6 +86,13 @@ function createGltf(objData) { transparent = diffuse[3] < 1.0; } + if (Array.isArray(ambient)) { + // If ambient color is [1, 1, 1] assume it is a multiplier and instead change to [0, 0, 0] + if (ambient[0] === 1.0 && ambient[1] === 1.0 && ambient[2] === 1.0) { + ambient = [0.0, 0.0, 0.0, 1.0]; + } + } + var doubleSided = transparent; if (!hasNormals) { diff --git a/specs/lib/createGltfSpec.js b/specs/lib/createGltfSpec.js index c509492..dd00f24 100644 --- a/specs/lib/createGltfSpec.js +++ b/specs/lib/createGltfSpec.js @@ -359,4 +359,13 @@ describe('createGltf', function() { var positionAccessor = gltf.accessors[primitive.attributes.POSITION]; expect(positionAccessor.count).toBe(vertexCount); }); + + it('ambient of [1, 1, 1] is treated as [0, 0, 0]', function() { + boxObjData.materials.Material.ambientColor = [1.0, 1.0, 1.0, 1.0]; + + var gltf = createGltf(boxObjData); + var ambient = gltf.materials.Material.extensions.KHR_materials_common.values.ambient; + + expect(ambient).toEqual([0.0, 0.0, 0.0, 1.0]); + }); }); From 403e4cf68e6ff060933630c2878532396a844376 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 25 Apr 2017 13:02:14 -0400 Subject: [PATCH 51/69] Spec improvements --- lib/obj2gltf.js | 25 ++++++++++++--------- specs/lib/loadObjSpec.js | 8 +++---- specs/lib/obj2gltfSpec.js | 46 ++++++++++++++++++++------------------- 3 files changed, 43 insertions(+), 36 deletions(-) diff --git a/lib/obj2gltf.js b/lib/obj2gltf.js index 3d4386b..9255a06 100644 --- a/lib/obj2gltf.js +++ b/lib/obj2gltf.js @@ -11,7 +11,6 @@ var loadObj = require('./loadObj'); var writeUris = require('./writeUris'); var fsExtraOutputJson = Promise.promisify(fsExtra.outputJson); -var fsExtraRemove = Promise.promisify(fsExtra.remove); var defaultValue = Cesium.defaultValue; var defined = Cesium.defined; @@ -85,7 +84,7 @@ function obj2gltf(objPath, gltfPath, options) { } gltfPath = path.join(path.dirname(gltfPath), modelName + extension); - var resourcesDirectory = options.bypassPipeline ? path.dirname(gltfPath) : getTempDirectory(); + var resourcesDirectory = options.bypassPipeline ? path.dirname(gltfPath) : obj2gltf._getTempDirectory(); var aoOptions = ao ? {} : undefined; var kmcOptions = kmc ? {} : undefined; @@ -121,23 +120,20 @@ function obj2gltf(objPath, gltfPath, options) { return GltfPipeline.processJSONToDisk(gltf, gltfPath, pipelineOptions); } }) - .then(function() { + .finally(function() { return cleanup(resourcesDirectory, options); }); } function cleanup(resourcesDirectory, options) { if (!options.bypassPipeline && options.separate) { - return fsExtraRemove(resourcesDirectory); + fsExtra.remove(resourcesDirectory, function () { + // Don't fail simply because we couldn't + // clean up the temporary files. + }); } } -function getTempDirectory() { - var tempDirectory = os.tmpdir(); - var randomId = uuid.v4(); - return path.join(tempDirectory, randomId); -} - /** * Default values that will be used when calling obj2gltf(options) unless specified in the options object. */ @@ -230,6 +226,15 @@ obj2gltf.defaults = { */ obj2gltf._outputJson = fsExtraOutputJson; +/** + * Exposed for testing + * + * @private + */ +obj2gltf._getTempDirectory = function () { + return path.join(os.tmpdir(), uuid()); +}; + /** * A callback function that logs messages. * @callback Logger diff --git a/specs/lib/loadObjSpec.js b/specs/lib/loadObjSpec.js index 3375e64..d41124c 100644 --- a/specs/lib/loadObjSpec.js +++ b/specs/lib/loadObjSpec.js @@ -62,6 +62,10 @@ function getImagePath(objPath, relativePath) { var defaultOptions = obj2gltf.defaults; describe('loadObj', function() { + beforeEach(function() { + spyOn(console, 'log'); + }); + it('loads obj with positions, normals, and uvs', function(done) { expect(loadObj(objUrl, defaultOptions) .then(function(data) { @@ -270,7 +274,6 @@ describe('loadObj', function() { }); it('loads obj with missing mtllib', function(done) { - spyOn(console, 'log'); expect(loadObj(objMissingMtllibUrl, defaultOptions) .then(function(data) { expect(data.materials).toEqual({}); @@ -288,8 +291,6 @@ describe('loadObj', function() { }); it('does not load resources outside of the obj directory when secure is true', function(done) { - spyOn(console, 'log'); - var options = clone(defaultOptions); options.secure = true; @@ -314,7 +315,6 @@ describe('loadObj', function() { }); it('loads obj with missing texture', function(done) { - spyOn(console, 'log'); expect(loadObj(objMissingTextureUrl, defaultOptions) .then(function(data) { var imagePath = getImagePath(objMissingTextureUrl, 'cesium.png'); diff --git a/specs/lib/obj2gltfSpec.js b/specs/lib/obj2gltfSpec.js index eda21f7..3cac5f9 100644 --- a/specs/lib/obj2gltfSpec.js +++ b/specs/lib/obj2gltfSpec.js @@ -1,6 +1,9 @@ 'use strict'; +var fsExtra = require('fs-extra'); var GltfPipeline = require('gltf-pipeline').Pipeline; +var os = require('os'); var path = require('path'); +var Promise = require('bluebird'); var obj2gltf = require('../../lib/obj2gltf'); var writeUris = require('../../lib/writeUris'); @@ -10,28 +13,33 @@ var glbPath = 'specs/data/box-textured/box-textured.glb'; var objPathNonExistent = 'specs/data/non-existent.obj'; describe('obj2gltf', function() { + var tempDirectory; + + beforeAll(function() { + expect(obj2gltf._getTempDirectory()).toContain(os.tmpdir()); + tempDirectory = path.join(os.tmpdir(), 'testPath'); + spyOn(obj2gltf, '_getTempDirectory').and.returnValue(tempDirectory); + spyOn(obj2gltf, '_outputJson'); + spyOn(writeUris, '_outputFile'); + spyOn(fsExtra, 'remove'); + }); + + beforeEach(function() { + spyOn(GltfPipeline, 'processJSONToDisk').and.returnValue(Promise.resolve()); + }); + it('converts an obj to gltf', function(done) { - var spy = spyOn(GltfPipeline, 'processJSONToDisk'); expect(obj2gltf(objPath, gltfPath) .then(function() { - var args = spy.calls.first().args; + var args = GltfPipeline.processJSONToDisk.calls.first().args; var gltf = args[0]; var outputPath = args[1]; + var options = args[2]; expect(path.normalize(outputPath)).toEqual(path.normalize(gltfPath)); expect(gltf).toBeDefined(); expect(gltf.images.cesium).toBeDefined(); - }), done).toResolve(); - }); - - it('uses default gltf-pipeline options', function(done) { - var spy = spyOn(GltfPipeline, 'processJSONToDisk'); - expect(obj2gltf(objPath, gltfPath) - .then(function() { - var args = spy.calls.first().args; - var options = args[2]; - expect(options.basePath).toBeDefined(); - delete options.basePath; // This will be a random temp directory expect(options).toEqual({ + basePath : tempDirectory, createDirectory : false, binary : false, embed : true, @@ -50,8 +58,6 @@ describe('obj2gltf', function() { }); it('sets options', function(done) { - var spy = spyOn(GltfPipeline, 'processJSONToDisk'); - spyOn(writeUris, '_outputFile'); var textureCompressionOptions = { format : 'dxt1', quality : 10 @@ -74,11 +80,10 @@ describe('obj2gltf', function() { expect(obj2gltf(objPath, gltfPath, options) .then(function() { - var args = spy.calls.first().args; + var args = GltfPipeline.processJSONToDisk.calls.first().args; var options = args[2]; - expect(options.basePath).toBeDefined(); - delete options.basePath; // This will be a random temp directory expect(options).toEqual({ + basePath : tempDirectory, createDirectory : false, binary : true, embed : false, @@ -98,18 +103,15 @@ describe('obj2gltf', function() { }); it('saves as binary if gltfPath has a .glb extension', function(done) { - var spy = spyOn(GltfPipeline, 'processJSONToDisk'); expect(obj2gltf(objPath, glbPath) .then(function() { - var args = spy.calls.first().args; + var args = GltfPipeline.processJSONToDisk.calls.first().args; var options = args[2]; expect(options.binary).toBe(true); }), done).toResolve(); }); it('bypassPipeline flag bypasses gltf-pipeline', function(done) { - spyOn(obj2gltf, '_outputJson'); - spyOn(GltfPipeline, 'processJSONToDisk'); var options = { bypassPipeline : true }; From 74f95cd61631bef1e0e21b50c1dd74a224afc822 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 25 Apr 2017 16:39:19 -0400 Subject: [PATCH 52/69] Bump to 1.1.0 --- .npmignore | 1 + CHANGES.md | 5 +++++ package.json | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.npmignore b/.npmignore index 202de55..26f16d5 100644 --- a/.npmignore +++ b/.npmignore @@ -5,6 +5,7 @@ /specs /test /output +.editorconfig .jshintrc .npmignore .travis.yml diff --git a/CHANGES.md b/CHANGES.md index 326fa8c..33d2b86 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ Change Log ========== +### 1.1.0 +* Added ability to convert the up-axis of the obj model. [#68](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/68) +* Fixed issues with an extra .bin file being saved when using `--separate`. [#62](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/62) +* Fixed issue where an ambient color of `[1, 1, 1]` overly brightens the converted model. [#70](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/70) + ### 1.0.0 2017-04-13 * Breaking changes diff --git a/package.json b/package.json index 6a45bfd..77b8c22 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obj2gltf", - "version": "1.0.0", + "version": "1.1.0", "description": "Convert OBJ model format to glTF", "license": "Apache-2.0", "contributors": [ From 9213047cbbc88e565bf2ee078ad8a89f2f56e1ca Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 25 Apr 2017 16:41:05 -0400 Subject: [PATCH 53/69] Fix CHANGES.md --- CHANGES.md | 7 ++++++- package.json | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 33d2b86..160e98d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,12 @@ Change Log ========== -### 1.1.0 +### 1.1.1 2017-04-25 + +* Fixed `CHANGES.md` formatting. + +### 1.1.0 2017-04-25 + * Added ability to convert the up-axis of the obj model. [#68](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/68) * Fixed issues with an extra .bin file being saved when using `--separate`. [#62](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/62) * Fixed issue where an ambient color of `[1, 1, 1]` overly brightens the converted model. [#70](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/70) diff --git a/package.json b/package.json index 77b8c22..338fc7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obj2gltf", - "version": "1.1.0", + "version": "1.1.1", "description": "Convert OBJ model format to glTF", "license": "Apache-2.0", "contributors": [ From 81201dcd03c568929b18ed55fbd07753d69bf32c Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 27 Apr 2017 13:35:47 -0400 Subject: [PATCH 54/69] Return Promise.reject instead of throwing error --- lib/obj2gltf.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/obj2gltf.js b/lib/obj2gltf.js index 0d1e2eb..260df96 100644 --- a/lib/obj2gltf.js +++ b/lib/obj2gltf.js @@ -15,6 +15,7 @@ var fsExtraOutputJson = Promise.promisify(fsExtra.outputJson); var defaultValue = Cesium.defaultValue; var defined = Cesium.defined; var DeveloperError = Cesium.DeveloperError; +var RuntimeError = Cesium.RuntimeError; module.exports = obj2gltf; @@ -85,7 +86,7 @@ function obj2gltf(objPath, gltfPath, options) { } if (binary && bypassPipeline) { - throw new DeveloperError('--bypassPipeline does not convert to binary glTF'); + return Promise.reject(new RuntimeError('--bypassPipeline does not convert to binary glTF')); } gltfPath = path.join(path.dirname(gltfPath), modelName + extension); From 86c604826c2843cf986fe2a9381557ac58f7adf7 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 18 May 2017 09:32:06 -0400 Subject: [PATCH 55/69] Treat alpha as 1.0 - Tr --- lib/loadMtl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/loadMtl.js b/lib/loadMtl.js index 6d3f506..5b5c72b 100644 --- a/lib/loadMtl.js +++ b/lib/loadMtl.js @@ -66,7 +66,7 @@ function loadMtl(mtlPath) { material.alpha = parseFloat(value); } else if (/^Tr /i.test(line)) { value = line.substring(3).trim(); - material.alpha = parseFloat(value); + material.alpha = 1.0 - parseFloat(value); } else if (/^map_Ka /i.test(line)) { material.ambientTexture = path.resolve(mtlDirectory, line.substring(7).trim()); } else if (/^map_Ke /i.test(line)) { From f62e2bd6cf89f337be3878fd1b34a2bacab4cf9d Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 18 May 2017 09:37:45 -0400 Subject: [PATCH 56/69] Fix test --- specs/data/box-complex-material/box-complex-material.mtl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/data/box-complex-material/box-complex-material.mtl b/specs/data/box-complex-material/box-complex-material.mtl index 3f69a9e..5c598ff 100644 --- a/specs/data/box-complex-material/box-complex-material.mtl +++ b/specs/data/box-complex-material/box-complex-material.mtl @@ -9,7 +9,7 @@ Ks 0.500000 0.500000 0.500000 Ke 0.100000 0.100000 0.100000 Ni 1.000000 d 0.900000 -Tr 0.900000 +Tr 0.100000 map_Ka ambient.gif map_Ke emission.jpg map_Kd diffuse.png From f141c2d9c94da90843594a8f683bc3395caa3249 Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Fri, 19 May 2017 11:37:33 -0400 Subject: [PATCH 57/69] Update npm dependencies A few npm dependencies were major versions behind, so this updates `yargs`, `fs-extra`, and `jasmin-spec-reporter` to their latest versions. The major change here is `fs-extra`, which now has promise implementations of all functions by default, this means there's no reason to manually `Promisify` a function any more, the result is less code overall. There is one important edge case, `fs-extra` uses built-in native Node promises, which do not have a `finally` function. If you start a promise change with an `fs-extra` function, you need to wrap it in `Promise.resolve` in order to make use of finally at the end (assuming you are using finally at all, if not you don't need to worry about it. The upside is that your code will always error if you forget to do this. --- lib/loadImage.js | 4 +--- lib/obj2gltf.js | 11 +---------- lib/writeUris.js | 13 ++----------- package.json | 6 +++--- specs/lib/createGltfSpec.js | 6 ++---- specs/lib/obj2gltfSpec.js | 8 ++++---- 6 files changed, 13 insertions(+), 35 deletions(-) diff --git a/lib/loadImage.js b/lib/loadImage.js index 8a3b686..c238a27 100644 --- a/lib/loadImage.js +++ b/lib/loadImage.js @@ -5,8 +5,6 @@ var path = require('path'); var PNG = require('pngjs').PNG; var Promise = require('bluebird'); -var fsExtraReadFile = Promise.promisify(fsExtra.readFile); - var defined = Cesium.defined; var WebGLConstants = Cesium.WebGLConstants; @@ -23,7 +21,7 @@ module.exports = loadImage; * @private */ function loadImage(imagePath, options) { - return fsExtraReadFile(imagePath) + return fsExtra.readFile(imagePath) .then(function(data) { var extension = path.extname(imagePath).toLowerCase(); diff --git a/lib/obj2gltf.js b/lib/obj2gltf.js index 260df96..33f6e89 100644 --- a/lib/obj2gltf.js +++ b/lib/obj2gltf.js @@ -10,8 +10,6 @@ var createGltf = require('./createGltf'); var loadObj = require('./loadObj'); var writeUris = require('./writeUris'); -var fsExtraOutputJson = Promise.promisify(fsExtra.outputJson); - var defaultValue = Cesium.defaultValue; var defined = Cesium.defined; var DeveloperError = Cesium.DeveloperError; @@ -121,7 +119,7 @@ function obj2gltf(objPath, gltfPath, options) { }) .then(function(gltf) { if (bypassPipeline) { - return obj2gltf._outputJson(gltfPath, gltf); + return fsExtra.outputJson(gltfPath, gltf); } else { return GltfPipeline.processJSONToDisk(gltf, gltfPath, pipelineOptions); } @@ -237,13 +235,6 @@ obj2gltf.defaults = { } }; -/** - * Exposed for testing - * - * @private - */ -obj2gltf._outputJson = fsExtraOutputJson; - /** * Exposed for testing * diff --git a/lib/writeUris.js b/lib/writeUris.js index 1a578fd..5987a72 100644 --- a/lib/writeUris.js +++ b/lib/writeUris.js @@ -5,8 +5,6 @@ var mime = require('mime'); var path = require('path'); var Promise = require('bluebird'); -var fsExtraOutputFile = Promise.promisify(fsExtra.outputFile); - var RuntimeError = Cesium.RuntimeError; module.exports = writeUris; @@ -88,7 +86,7 @@ function writeSeparateBuffer(gltf, resourcesDirectory, name) { var bufferUri = name + '.bin'; buffer.uri = bufferUri; var bufferPath = path.join(resourcesDirectory, bufferUri); - return writeUris._outputFile(bufferPath, source); + return fsExtra.outputFile(bufferPath, source); } function writeSeparateTextures(gltf, resourcesDirectory) { @@ -99,7 +97,7 @@ function writeSeparateTextures(gltf, resourcesDirectory) { var imageUri = image.name + extras.extension; image.uri = imageUri; var imagePath = path.join(resourcesDirectory, imageUri); - return writeUris._outputFile(imagePath, extras.source); + return fsExtra.outputFile(imagePath, extras.source); }, {concurrency : 10}); } @@ -119,10 +117,3 @@ function writeEmbeddedTextures(gltf) { } } } - -/** - * Exposed for testing. - * - * @private - */ -writeUris._outputFile = fsExtraOutputFile; diff --git a/package.json b/package.json index 338fc7c..0d37800 100644 --- a/package.json +++ b/package.json @@ -29,12 +29,12 @@ "bluebird": "^3.4.7", "cesium": "^1.31.0", "event-stream": "^3.3.4", - "fs-extra": "^2.0.0", + "fs-extra": "^3.0.1", "gltf-pipeline": "^0.1.0-alpha11", "mime": "^1.3.4", "pngjs": "^3.0.1", "uuid": "^3.0.1", - "yargs": "^7.0.1" + "yargs": "^8.0.1" }, "devDependencies": { "coveralls": "^2.12.0", @@ -42,7 +42,7 @@ "gulp-jshint": "^2.0.4", "istanbul": "^0.4.5", "jasmine": "^2.5.3", - "jasmine-spec-reporter": "^3.2.0", + "jasmine-spec-reporter": "^4.1.0", "jsdoc": "^3.4.3", "jshint": "^2.9.4", "jshint-stylish": "^2.2.1", diff --git a/specs/lib/createGltfSpec.js b/specs/lib/createGltfSpec.js index b90d5bc..1e750ee 100644 --- a/specs/lib/createGltfSpec.js +++ b/specs/lib/createGltfSpec.js @@ -13,8 +13,6 @@ var writeUris = require('../../lib/writeUris'); var clone = Cesium.clone; var WebGLConstants = Cesium.WebGLConstants; -var fsExtraReadJson = Promise.promisify(fsExtra.readJson); - var boxObjUrl = 'specs/data/box/box.obj'; var groupObjUrl = 'specs/data/box-objects-groups-materials/box-objects-groups-materials.obj'; var boxGltfUrl = 'specs/data/box/box.gltf'; @@ -49,11 +47,11 @@ describe('createGltf', function() { .then(function(data) { groupObjData = data; }), - fsExtraReadJson(boxGltfUrl) + fsExtra.readJson(boxGltfUrl) .then(function(gltf) { boxGltf = gltf; }), - fsExtraReadJson(groupGltfUrl) + fsExtra.readJson(groupGltfUrl) .then(function(gltf) { groupGltf = gltf; }), diff --git a/specs/lib/obj2gltfSpec.js b/specs/lib/obj2gltfSpec.js index a25ca3f..d2aa73d 100644 --- a/specs/lib/obj2gltfSpec.js +++ b/specs/lib/obj2gltfSpec.js @@ -19,8 +19,8 @@ describe('obj2gltf', function() { expect(obj2gltf._getTempDirectory()).toContain(os.tmpdir()); tempDirectory = path.join(os.tmpdir(), 'testPath'); spyOn(obj2gltf, '_getTempDirectory').and.returnValue(tempDirectory); - spyOn(obj2gltf, '_outputJson'); - spyOn(writeUris, '_outputFile'); + spyOn(fsExtra, 'outputJson'); + spyOn(fsExtra, 'outputFile'); spyOn(fsExtra, 'remove'); }); @@ -100,7 +100,7 @@ describe('obj2gltf', function() { textureCompressionOptions : textureCompressionOptions, preserve : false }); - expect(writeUris._outputFile.calls.count()).toBe(2); // Saves out .png and .bin + expect(fsExtra.outputFile.calls.count()).toBe(2); // Saves out .png and .bin }), done).toResolve(); }); @@ -119,7 +119,7 @@ describe('obj2gltf', function() { }; expect(obj2gltf(objPath, gltfPath, options) .then(function() { - expect(obj2gltf._outputJson).toHaveBeenCalled(); + expect(fsExtra.outputJson).toHaveBeenCalled(); expect(GltfPipeline.processJSONToDisk).not.toHaveBeenCalled(); }), done).toResolve(); }); From c8adca33245de26ab827dd173084d4eff44d2632 Mon Sep 17 00:00:00 2001 From: Ottavio Hartman Date: Mon, 12 Jun 2017 11:42:37 -0400 Subject: [PATCH 58/69] Replace JSHint with ESLint shareable config --- .eslintrc.json | 3 + .idea/inspectionProfiles/Project_Default.xml | 2 +- .idea/jsLinters/jshint.xml | 83 -------------------- .jshintrc | 60 -------------- .npmignore | 2 +- .travis.yml | 2 +- README.md | 8 +- gulpfile.js | 23 +++--- index.js | 1 + package.json | 9 +-- specs/.eslintrc.json | 9 +++ specs/.jshintrc | 5 -- specs/matchers/addDefaultMatchers.js | 4 +- specs/matchers/customizeJasmine.js | 3 +- specs/matchers/equals.js | 3 +- specs/matchers/equalsMethodEqualityTester.js | 3 +- 16 files changed, 45 insertions(+), 175 deletions(-) create mode 100644 .eslintrc.json delete mode 100644 .idea/jsLinters/jshint.xml delete mode 100644 .jshintrc create mode 100644 specs/.eslintrc.json delete mode 100644 specs/.jshintrc diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..fcf4081 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "cesium/node" +} diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index eff7139..c6cc8c8 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -1,6 +1,6 @@ \ No newline at end of file diff --git a/.idea/jsLinters/jshint.xml b/.idea/jsLinters/jshint.xml deleted file mode 100644 index aed467c..0000000 --- a/.idea/jsLinters/jshint.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index bdff25a..0000000 --- a/.jshintrc +++ /dev/null @@ -1,60 +0,0 @@ -{ - "bitwise": false, - "camelcase": false, - "curly": true, - "eqeqeq": true, - "forin": true, - "freeze": true, - "immed": true, - "latedef": "nofunc", - "newcap": true, - "noarg": true, - "nonbsp": true, - "nonew": true, - "plusplus": false, - "quotmark": false, - "undef": true, - "unused": "strict", - "strict": true, - "asi": false, - "boss": false, - "debug": false, - "eqnull": false, - "moz": false, - "evil": false, - "expr": false, - "funcscope": false, - "globalstrict": false, - "iterator": false, - "lastsemic": false, - "laxbreak": false, - "laxcomma": false, - "loopfunc": false, - "multistr": true, - "noyield": false, - "notypeof": false, - "proto": false, - "scripturl": false, - "shadow": false, - "sub": false, - "supernew": false, - "validthis": false, - "browser": false, - "browserify": false, - "couch": false, - "devel": true, - "dojo": false, - "jasmine": false, - "jquery": false, - "mocha": true, - "mootools": false, - "node": true, - "nonstandard": false, - "prototypejs": false, - "qunit": false, - "rhino": false, - "shelljs": false, - "worker": false, - "wsh": false, - "yui": false -} diff --git a/.npmignore b/.npmignore index 26f16d5..8d9d85c 100644 --- a/.npmignore +++ b/.npmignore @@ -6,7 +6,7 @@ /test /output .editorconfig -.jshintrc +.eslintrc.json .npmignore .travis.yml gulpfile.js diff --git a/.travis.yml b/.travis.yml index a066d24..8e939a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ node_js: - "4" - "6" script: - - npm run jsHint -- --failTaskOnError + - npm run eslint -- --failTaskOnError - npm run test -- --failTaskOnError --suppressPassed after_success: diff --git a/README.md b/README.md index 565a4bb..0d9e3cd 100644 --- a/README.md +++ b/README.md @@ -55,13 +55,13 @@ Run the tests: ``` npm run test ``` -To run JSHint on the entire codebase, run: +To run ESLint on the entire codebase, run: ``` -npm run jsHint +npm run eslint ``` -To run JSHint 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 jsHint-watch +npm run eslint-watch ``` ## Running Test Coverage diff --git a/gulpfile.js b/gulpfile.js index 8d12058..01c8b5c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -4,7 +4,7 @@ var Cesium = require('cesium'); var child_process = require('child_process'); var fsExtra = require('fs-extra'); var gulp = require('gulp'); -var gulpJshint = require('gulp-jshint'); +var eslint = require('gulp-eslint'); var Jasmine = require('jasmine'); var JasmineSpecReporter = require('jasmine-spec-reporter').SpecReporter; var open = require('open'); @@ -20,23 +20,26 @@ var environmentSeparator = process.platform === 'win32' ? ';' : ':'; var nodeBinaries = path.join(__dirname, 'node_modules', '.bin'); process.env.PATH += environmentSeparator + nodeBinaries; -var jsHintFiles = ['**/*.js', '!node_modules/**', '!coverage/**', '!doc/**']; +var esLintFiles = ['**/*.js', '!node_modules/**', '!coverage/**', '!doc/**']; var specFiles = ['**/*.js', '!node_modules/**', '!coverage/**', '!doc/**', '!bin/**']; -gulp.task('jsHint', function () { - var stream = gulp.src(jsHintFiles) - .pipe(gulpJshint()) - .pipe(gulpJshint.reporter('jshint-stylish')); - +gulp.task('eslint', function () { + var stream = gulp.src(esLintFiles) + .pipe(eslint()) + .pipe(eslint.format()); if (argv.failTaskOnError) { - stream = stream.pipe(gulpJshint.reporter('fail')); + stream = stream.pipe(eslint.failAfterError()); } return stream; }); -gulp.task('jsHint-watch', function () { - gulp.watch(jsHintFiles, ['jsHint']); +gulp.task('eslint-watch', function() { + gulp.watch(esLintFiles).on('change', function(event) { + gulp.src(event.path) + .pipe(eslint()) + .pipe(eslint.format()); + }); }); gulp.task('test', function (done) { diff --git a/index.js b/index.js index f0c4b18..b157a83 100644 --- a/index.js +++ b/index.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('./lib/obj2gltf'); diff --git a/package.json b/package.json index 0d37800..40fd641 100644 --- a/package.json +++ b/package.json @@ -38,21 +38,20 @@ }, "devDependencies": { "coveralls": "^2.12.0", + "eslint-config-cesium": "^1.0.0", "gulp": "^3.9.1", - "gulp-jshint": "^2.0.4", + "gulp-eslint": "^4.0.0", "istanbul": "^0.4.5", "jasmine": "^2.5.3", "jasmine-spec-reporter": "^4.1.0", "jsdoc": "^3.4.3", - "jshint": "^2.9.4", - "jshint-stylish": "^2.2.1", "open": "^0.0.5", "requirejs": "^2.3.3" }, "scripts": { "jsdoc": "jsdoc ./lib -R ./README.md -d doc", - "jsHint": "gulp jsHint", - "jsHint-watch": "gulp jsHint-watch", + "eslint": "gulp eslint", + "eslint-watch": "gulp eslint-watch", "test": "gulp test", "test-watch": "gulp test-watch", "coverage": "gulp coverage", diff --git a/specs/.eslintrc.json b/specs/.eslintrc.json new file mode 100644 index 0000000..92eb4d1 --- /dev/null +++ b/specs/.eslintrc.json @@ -0,0 +1,9 @@ +{ + "extends": "../.eslintrc.json", + "env": { + "jasmine": true + }, + "rules": { + "no-unused-vars": "off" + } +} diff --git a/specs/.jshintrc b/specs/.jshintrc deleted file mode 100644 index d921aae..0000000 --- a/specs/.jshintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../.jshintrc", - "jasmine": true, - "unused": false -} diff --git a/specs/matchers/addDefaultMatchers.js b/specs/matchers/addDefaultMatchers.js index a1c6410..7a6dcc2 100644 --- a/specs/matchers/addDefaultMatchers.js +++ b/specs/matchers/addDefaultMatchers.js @@ -1,6 +1,6 @@ //This file is a copy of https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Specs/addDefaultMatchers.js -/*global define*/ -/*jshint unused:false*/ +/*eslint strict: ["error", "function"]*/ +/*eslint-env amd*/ define([ './equals', 'Cesium/Core/defined', diff --git a/specs/matchers/customizeJasmine.js b/specs/matchers/customizeJasmine.js index 8630965..a7b51c2 100644 --- a/specs/matchers/customizeJasmine.js +++ b/specs/matchers/customizeJasmine.js @@ -1,4 +1,5 @@ -/*global define*/ +/*eslint strict: ["error", "function"]*/ +/*eslint-env amd*/ define([ './addDefaultMatchers', './equalsMethodEqualityTester' diff --git a/specs/matchers/equals.js b/specs/matchers/equals.js index 99203d0..3ce5e26 100644 --- a/specs/matchers/equals.js +++ b/specs/matchers/equals.js @@ -1,5 +1,6 @@ //This file is a copy of https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Specs/equals.js -/*global define*/ +/*eslint strict: ["error", "function"]*/ +/*eslint-env amd*/ define([ 'Cesium/Core/FeatureDetection' ], function( diff --git a/specs/matchers/equalsMethodEqualityTester.js b/specs/matchers/equalsMethodEqualityTester.js index b9fd40e..4b61895 100644 --- a/specs/matchers/equalsMethodEqualityTester.js +++ b/specs/matchers/equalsMethodEqualityTester.js @@ -1,5 +1,6 @@ //This file is a copy of https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Specs/equalsMethodEqualityTester.js -/*global define*/ +/*eslint strict: ["error", "function"]*/ +/*eslint-env amd*/ define([ 'Cesium/Core/defined' ], function( From f2c3e05d8f98293764ef1f96b7f5104400fc5f75 Mon Sep 17 00:00:00 2001 From: Ottavio Hartman Date: Mon, 12 Jun 2017 15:10:35 -0400 Subject: [PATCH 59/69] Enable no-unused-vars and fix eslint errors --- specs/.eslintrc.json | 3 --- specs/lib/obj2gltfSpec.js | 1 - specs/matchers/addDefaultMatchers.js | 1 + 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/specs/.eslintrc.json b/specs/.eslintrc.json index 92eb4d1..297a41b 100644 --- a/specs/.eslintrc.json +++ b/specs/.eslintrc.json @@ -2,8 +2,5 @@ "extends": "../.eslintrc.json", "env": { "jasmine": true - }, - "rules": { - "no-unused-vars": "off" } } diff --git a/specs/lib/obj2gltfSpec.js b/specs/lib/obj2gltfSpec.js index d2aa73d..6ef53b0 100644 --- a/specs/lib/obj2gltfSpec.js +++ b/specs/lib/obj2gltfSpec.js @@ -5,7 +5,6 @@ var os = require('os'); var path = require('path'); var Promise = require('bluebird'); var obj2gltf = require('../../lib/obj2gltf'); -var writeUris = require('../../lib/writeUris'); var objPath = 'specs/data/box-textured/box-textured.obj'; var gltfPath = 'specs/data/box-textured/box-textured.gltf'; diff --git a/specs/matchers/addDefaultMatchers.js b/specs/matchers/addDefaultMatchers.js index 7a6dcc2..a883689 100644 --- a/specs/matchers/addDefaultMatchers.js +++ b/specs/matchers/addDefaultMatchers.js @@ -1,6 +1,7 @@ //This file is a copy of https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Specs/addDefaultMatchers.js /*eslint strict: ["error", "function"]*/ /*eslint-env amd*/ +/*eslint-disable no-unused-vars*/ define([ './equals', 'Cesium/Core/defined', From 23d243cda9d0b5295dea1dd71e67d7dbcd40decc Mon Sep 17 00:00:00 2001 From: Patrick Cozzi Date: Wed, 14 Jun 2017 07:38:15 -0400 Subject: [PATCH 60/69] Update LICENSE.md so GitHub reports Apache 2.0 license --- LICENSE.md | 205 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 201 insertions(+), 4 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 9ca92f4..c8c9a9f 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,10 +1,207 @@ -Copyright 2016 Analytical Graphics, Inc. +Copyright 2016-2017 Analytical Graphics, Inc. and Contributors -Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -http://www.apache.org/licenses/LICENSE-2.0 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016-2017 Analytical Graphics, Inc. and Contributors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. -Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Third-Party Code ================ From d2e5df2d77c346d767ceb2eae14742e6b5f40364 Mon Sep 17 00:00:00 2001 From: Patrick Cozzi Date: Wed, 14 Jun 2017 08:22:41 -0400 Subject: [PATCH 61/69] Fix typo --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 40fd641..f7dd61e 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "license": "Apache-2.0", "contributors": [ { - "name": "Analytical Graphics, Inc., and Contributors", + "name": "Analytical Graphics, Inc. and Contributors", "url": "https://github.com/AnalyticalGraphicsInc/obj2gltf/graphs/contributors" } ], From d1ac5816d4f37d4c2032723f829079b1e0aa1b44 Mon Sep 17 00:00:00 2001 From: Rachel Hwang Date: Tue, 13 Jun 2017 14:22:54 -0400 Subject: [PATCH 62/69] make mip-mapping on by default --- CHANGES.md | 1 + lib/createGltf.js | 2 +- specs/lib/createGltfSpec.js | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 160e98d..598251c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ Change Log ### 1.1.1 2017-04-25 * Fixed `CHANGES.md` formatting. +* Change texture sampling to use NEAREST_MIPMAP_LINEAR by default [#83](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/83). ### 1.1.0 2017-04-25 diff --git a/lib/createGltf.js b/lib/createGltf.js index 33191c8..fbcce60 100644 --- a/lib/createGltf.js +++ b/lib/createGltf.js @@ -126,7 +126,7 @@ function createGltf(objData) { if (Object.keys(images).length > 0) { gltf.samplers[samplerId] = { magFilter : WebGLConstants.LINEAR, - minFilter : WebGLConstants.LINEAR, + minFilter : WebGLConstants.NEAREST_MIPMAP_LINEAR, wrapS : WebGLConstants.REPEAT, wrapT : WebGLConstants.REPEAT }; diff --git a/specs/lib/createGltfSpec.js b/specs/lib/createGltfSpec.js index 1e750ee..04b4b1c 100644 --- a/specs/lib/createGltfSpec.js +++ b/specs/lib/createGltfSpec.js @@ -143,7 +143,7 @@ describe('createGltf', function() { expect(gltf.samplers.sampler).toEqual({ magFilter : WebGLConstants.LINEAR, - minFilter : WebGLConstants.LINEAR, + minFilter : WebGLConstants.NEAREST_MIPMAP_LINEAR, wrapS : WebGLConstants.REPEAT, wrapT : WebGLConstants.REPEAT }); From dbe455fe14e84bf9c285b004c495b50018d778f1 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 14 Jun 2017 20:00:48 -0400 Subject: [PATCH 63/69] Fix CHANGES.md --- CHANGES.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 598251c..b19fe47 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,13 @@ Change Log ========== +### Next release + +* Change texture sampling to use NEAREST_MIPMAP_LINEAR by default [#83](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/83). + ### 1.1.1 2017-04-25 * Fixed `CHANGES.md` formatting. -* Change texture sampling to use NEAREST_MIPMAP_LINEAR by default [#83](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/83). ### 1.1.0 2017-04-25 From 7946fecbeb69566bd560984dafb395c9f42f8447 Mon Sep 17 00:00:00 2001 From: Ottavio Hartman Date: Tue, 20 Jun 2017 13:24:03 -0400 Subject: [PATCH 64/69] Replace Istanbul with NYC --- .gitignore | 1 + README.md | 2 +- gulpfile.js | 6 +++--- package.json | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 8e46cf7..6e76bb2 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ doc output test *.tgz +.nyc_output diff --git a/README.md b/README.md index 0d9e3cd..7b5e786 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ npm run eslint-watch ## Running Test Coverage -Coverage uses [istanbul](https://github.com/gotwarlost/istanbul). Run: +Coverage uses [nyc](https://github.com/istanbuljs/nyc). Run: ``` npm run coverage ``` diff --git a/gulpfile.js b/gulpfile.js index 01c8b5c..2cfbded 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -70,9 +70,9 @@ gulp.task('test-watch', function () { gulp.task('coverage', function () { fsExtra.removeSync('coverage/server'); - child_process.execSync('istanbul' + - ' cover' + - ' --include-all-sources' + + child_process.execSync('nyc' + + ' --all' + + ' --reporter=lcov' + ' --dir coverage' + ' -x "specs/**" -x "coverage/**" -x "doc/**" -x "bin/**" -x "index.js" -x "gulpfile.js"' + ' node_modules/jasmine/bin/jasmine.js' + diff --git a/package.json b/package.json index f7dd61e..b091061 100644 --- a/package.json +++ b/package.json @@ -41,10 +41,10 @@ "eslint-config-cesium": "^1.0.0", "gulp": "^3.9.1", "gulp-eslint": "^4.0.0", - "istanbul": "^0.4.5", "jasmine": "^2.5.3", "jasmine-spec-reporter": "^4.1.0", "jsdoc": "^3.4.3", + "nyc": "^11.0.2", "open": "^0.0.5", "requirejs": "^2.3.3" }, From d728340ad42d40058d3749cfe695acd64ac70bb4 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 22 Jun 2017 20:56:32 -0400 Subject: [PATCH 65/69] Keep diffuse intact so that shader generation for generateNormals works correctly --- lib/createGltf.js | 8 ++++---- lib/obj2gltf.js | 3 ++- specs/lib/createGltfSpec.js | 38 ++++++++++++++++++------------------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/lib/createGltf.js b/lib/createGltf.js index fbcce60..f7bc747 100644 --- a/lib/createGltf.js +++ b/lib/createGltf.js @@ -17,7 +17,7 @@ module.exports = createGltf; * * @private */ -function createGltf(objData) { +function createGltf(objData, options) { var nodes = objData.nodes; var materials = objData.materials; var images = objData.images; @@ -67,7 +67,7 @@ function createGltf(objData) { return 'texture_' + getImageId(imagePath); } - function createMaterial(material, hasNormals) { + function createMaterial(material, hasNormals, options) { var ambient = defaultValue(defaultValue(getTextureId(material.ambientTexture), material.ambientColor)); var diffuse = defaultValue(defaultValue(getTextureId(material.diffuseTexture), material.diffuseColor)); var emission = defaultValue(defaultValue(getTextureId(material.emissionTexture), material.emissionColor)); @@ -95,7 +95,7 @@ function createGltf(objData) { var doubleSided = transparent; - if (!hasNormals) { + if (!hasNormals && !options.generateNormals) { // Constant technique only factors in ambient and emission sources - set emission to diffuse emission = diffuse; diffuse = [0, 0, 0, 1]; @@ -303,7 +303,7 @@ function createGltf(objData) { } if (!defined(gltfMaterial)) { - gltf.materials[materialId] = createMaterial(material, hasNormals); + gltf.materials[materialId] = createMaterial(material, hasNormals, options); } gltfMeshPrimitives.push({ diff --git a/lib/obj2gltf.js b/lib/obj2gltf.js index 33f6e89..14a1bfd 100644 --- a/lib/obj2gltf.js +++ b/lib/obj2gltf.js @@ -61,6 +61,7 @@ function obj2gltf(objPath, gltfPath, options) { var outputUpAxis = defaultValue(options.outputUpAxis, defaults.outputUpAxis); var logger = defaultValue(options.logger, defaults.logger); + options.generateNormals = generateNormals; options.separate = separate; options.separateTextures = separateTextures; options.checkTransparency = checkTransparency; @@ -112,7 +113,7 @@ function obj2gltf(objPath, gltfPath, options) { return loadObj(objPath, options) .then(function(objData) { - return createGltf(objData); + return createGltf(objData, options); }) .then(function(gltf) { return writeUris(gltf, gltfPath, resourcesDirectory, options); diff --git a/specs/lib/createGltfSpec.js b/specs/lib/createGltfSpec.js index 04b4b1c..992c9cb 100644 --- a/specs/lib/createGltfSpec.js +++ b/specs/lib/createGltfSpec.js @@ -67,7 +67,7 @@ describe('createGltf', function() { }); it('simple gltf', function(done) { - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); expect(writeUris(gltf, boxGltfUrl, path.dirname(boxGltfUrl), defaultOptions) .then(function() { expect(gltf).toEqual(boxGltf); @@ -75,7 +75,7 @@ describe('createGltf', function() { }); it('multiple nodes, meshes, and primitives', function(done) { - var gltf = createGltf(groupObjData); + var gltf = createGltf(groupObjData, defaultOptions); expect(writeUris(gltf, groupGltfUrl, path.dirname(groupGltfUrl), defaultOptions) .then(function() { @@ -97,7 +97,7 @@ describe('createGltf', function() { it('sets default material values', function() { boxObjData.materials.Material = new Material(); - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var material = gltf.materials.Material; var kmc = material.extensions.KHR_materials_common; var values = kmc.values; @@ -116,7 +116,7 @@ describe('createGltf', function() { boxObjData.materials.Material = material; boxObjData.images[diffuseTextureUrl] = diffuseTexture; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc = gltf.materials.Material.extensions.KHR_materials_common; var texture = gltf.textures.texture_cesium; var image = gltf.images.cesium; @@ -154,7 +154,7 @@ describe('createGltf', function() { material.alpha = 0.4; boxObjData.materials.Material = material; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 0.4]); @@ -171,7 +171,7 @@ describe('createGltf', function() { boxObjData.images[diffuseTextureUrl] = diffuseTexture; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.values.diffuse).toEqual('texture_cesium'); @@ -187,7 +187,7 @@ describe('createGltf', function() { boxObjData.images[transparentDiffuseTextureUrl] = transparentDiffuseTexture; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.values.diffuse).toBe('texture_diffuse'); @@ -202,7 +202,7 @@ describe('createGltf', function() { material.specularShininess = 0.1; boxObjData.materials.Material = material; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.technique).toBe('PHONG'); @@ -219,7 +219,7 @@ describe('createGltf', function() { boxObjData.images[diffuseTextureUrl] = diffuseTexture; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.technique).toBe('CONSTANT'); @@ -231,7 +231,7 @@ describe('createGltf', function() { material.diffuseTexture = diffuseTextureUrl; boxObjData.materials.Material = material; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]); @@ -241,7 +241,7 @@ describe('createGltf', function() { boxObjData.nodes[0].meshes[0].primitives[0].material = undefined; // Creates a material called "default" - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); expect(gltf.materials.default).toBeDefined(); var kmc = gltf.materials.default.extensions.KHR_materials_common; expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]); @@ -251,7 +251,7 @@ describe('createGltf', function() { boxObjData.materials = {}; // Uses the original name of the material - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]); @@ -262,7 +262,7 @@ describe('createGltf', function() { boxObjData.nodes.push(duplicateBoxObjData.nodes[0]); boxObjData.nodes[1].meshes[0].normals.length = 0; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc1 = gltf.materials.Material.extensions.KHR_materials_common; var kmc2 = gltf.materials.Material_constant.extensions.KHR_materials_common; @@ -275,7 +275,7 @@ describe('createGltf', function() { boxObjData.nodes.push(duplicateBoxObjData.nodes[0]); boxObjData.nodes[0].meshes[0].normals.length = 0; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc1 = gltf.materials.Material.extensions.KHR_materials_common; var kmc2 = gltf.materials.Material_shaded.extensions.KHR_materials_common; @@ -286,7 +286,7 @@ describe('createGltf', function() { it('runs without normals', function() { boxObjData.nodes[0].meshes[0].normals.length = 0; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes; expect(attributes.POSITION).toBeDefined(); expect(attributes.NORMAL).toBeUndefined(); @@ -296,7 +296,7 @@ describe('createGltf', function() { it('runs without uvs', function() { boxObjData.nodes[0].meshes[0].uvs.length = 0; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes; expect(attributes.POSITION).toBeDefined(); expect(attributes.NORMAL).toBeDefined(); @@ -307,7 +307,7 @@ describe('createGltf', function() { boxObjData.nodes[0].meshes[0].normals.length = 0; boxObjData.nodes[0].meshes[0].uvs.length = 0; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes; expect(attributes.POSITION).toBeDefined(); expect(attributes.NORMAL).toBeUndefined(); @@ -347,7 +347,7 @@ describe('createGltf', function() { var indicesLength = mesh.primitives[0].indices.length; var vertexCount = mesh.positions.length / 3; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var primitive = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0]; var indicesAccessor = gltf.accessors[primitive.indices]; expect(indicesAccessor.count).toBe(indicesLength); @@ -361,7 +361,7 @@ describe('createGltf', function() { it('ambient of [1, 1, 1] is treated as [0, 0, 0]', function() { boxObjData.materials.Material.ambientColor = [1.0, 1.0, 1.0, 1.0]; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var ambient = gltf.materials.Material.extensions.KHR_materials_common.values.ambient; expect(ambient).toEqual([0.0, 0.0, 0.0, 1.0]); From d2eee3e4c56a4a0e536f4a9c013697b23a753ac4 Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Fri, 23 Jun 2017 10:34:21 -0400 Subject: [PATCH 66/69] Update CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index b19fe47..63efc16 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ Change Log ### Next release * Change texture sampling to use NEAREST_MIPMAP_LINEAR by default [#83](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/83). +* Fixed normal generation. ### 1.1.1 2017-04-25 From 644c281e9cd4adff09223d24650aa4145cd95b9b Mon Sep 17 00:00:00 2001 From: Ottavio Hartman Date: Fri, 23 Jun 2017 13:42:34 -0400 Subject: [PATCH 67/69] Switch from gulp-eslint to the ESLint CLI. Switch to eslint-config-cesium 2.0. --- .eslintignore | 3 +++ .gitignore | 1 + .travis.yml | 2 +- gulpfile.js | 21 --------------------- lib/obj2gltf.js | 3 +-- package.json | 7 +++---- 6 files changed, 9 insertions(+), 28 deletions(-) create mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..0c85304 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +node_modules/** +coverage/** +doc/** diff --git a/.gitignore b/.gitignore index 6e76bb2..f7e2d78 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ npm-debug.log .idea/tasks.xml # Generate data +.eslintcache coverage doc output diff --git a/.travis.yml b/.travis.yml index 8e939a3..4264c75 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ node_js: - "4" - "6" script: - - npm run eslint -- --failTaskOnError + - npm run eslint - npm run test -- --failTaskOnError --suppressPassed after_success: diff --git a/gulpfile.js b/gulpfile.js index 2cfbded..773c8b9 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -4,7 +4,6 @@ var Cesium = require('cesium'); var child_process = require('child_process'); var fsExtra = require('fs-extra'); var gulp = require('gulp'); -var eslint = require('gulp-eslint'); var Jasmine = require('jasmine'); var JasmineSpecReporter = require('jasmine-spec-reporter').SpecReporter; var open = require('open'); @@ -20,28 +19,8 @@ var environmentSeparator = process.platform === 'win32' ? ';' : ':'; var nodeBinaries = path.join(__dirname, 'node_modules', '.bin'); process.env.PATH += environmentSeparator + nodeBinaries; -var esLintFiles = ['**/*.js', '!node_modules/**', '!coverage/**', '!doc/**']; var specFiles = ['**/*.js', '!node_modules/**', '!coverage/**', '!doc/**', '!bin/**']; -gulp.task('eslint', function () { - var stream = gulp.src(esLintFiles) - .pipe(eslint()) - .pipe(eslint.format()); - if (argv.failTaskOnError) { - stream = stream.pipe(eslint.failAfterError()); - } - - return stream; -}); - -gulp.task('eslint-watch', function() { - gulp.watch(esLintFiles).on('change', function(event) { - gulp.src(event.path) - .pipe(eslint()) - .pipe(eslint.format()); - }); -}); - gulp.task('test', function (done) { var jasmine = new Jasmine(); jasmine.loadConfigFile('specs/jasmine.json'); diff --git a/lib/obj2gltf.js b/lib/obj2gltf.js index 14a1bfd..86cac90 100644 --- a/lib/obj2gltf.js +++ b/lib/obj2gltf.js @@ -121,9 +121,8 @@ function obj2gltf(objPath, gltfPath, options) { .then(function(gltf) { if (bypassPipeline) { return fsExtra.outputJson(gltfPath, gltf); - } else { - return GltfPipeline.processJSONToDisk(gltf, gltfPath, pipelineOptions); } + return GltfPipeline.processJSONToDisk(gltf, gltfPath, pipelineOptions); }) .finally(function() { return cleanup(resourcesDirectory, options); diff --git a/package.json b/package.json index b091061..62487b3 100644 --- a/package.json +++ b/package.json @@ -38,9 +38,9 @@ }, "devDependencies": { "coveralls": "^2.12.0", - "eslint-config-cesium": "^1.0.0", + "eslint": "^4.1.1", + "eslint-config-cesium": "^2.0.0", "gulp": "^3.9.1", - "gulp-eslint": "^4.0.0", "jasmine": "^2.5.3", "jasmine-spec-reporter": "^4.1.0", "jsdoc": "^3.4.3", @@ -50,8 +50,7 @@ }, "scripts": { "jsdoc": "jsdoc ./lib -R ./README.md -d doc", - "eslint": "gulp eslint", - "eslint-watch": "gulp eslint-watch", + "eslint": "eslint \"./**/*.js\" --cache --quiet", "test": "gulp test", "test-watch": "gulp test-watch", "coverage": "gulp coverage", From 24513be7b8f9370a0bb6707a21b8c7ada0b57a98 Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Wed, 28 Jun 2017 08:34:42 -0400 Subject: [PATCH 68/69] Clean up .gitignore and .npmignore There were some outdated and missing entries. --- .gitignore | 2 -- .npmignore | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index f7e2d78..53cace9 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,5 @@ npm-debug.log .eslintcache coverage doc -output -test *.tgz .nyc_output diff --git a/.npmignore b/.npmignore index 8d9d85c..ae3d01d 100644 --- a/.npmignore +++ b/.npmignore @@ -1,12 +1,12 @@ /.idea /coverage /doc -/output /specs -/test -/output .editorconfig +.eslintcache +.eslintignore .eslintrc.json +.nyc_output .npmignore .travis.yml gulpfile.js From 912ab5edff3e9b80d19f3ae0b068d9eb581d4325 Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Wed, 28 Jun 2017 13:15:56 -0400 Subject: [PATCH 69/69] Remove dependency on event-stream Node has built in functions for reading lines from a file, so there's no need to depend on `event-stream` just for it. --- LICENSE.md | 29 ----------------------------- lib/readLines.js | 19 ++++++++++--------- package.json | 1 - 3 files changed, 10 insertions(+), 39 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index c8c9a9f..9fdda01 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -246,35 +246,6 @@ http://cesiumjs.org/ See https://github.com/AnalyticalGraphicsInc/cesium/blob/master/LICENSE.md -### event-stream - -https://www.npmjs.com/package/event-stream - -> The MIT License (MIT) -> -> Copyright (c) 2011 Dominic Tarr -> -> Permission is hereby granted, free of charge, -> to any person obtaining a copy of this software and -> associated documentation files (the "Software"), to -> deal in the Software without restriction, including -> without limitation the rights to use, copy, modify, -> merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom -> the Software is furnished to do so, -> subject to the following conditions: -> -> The above copyright notice and this permission notice -> shall be included in all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -> ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ### fs-extra https://www.npmjs.com/package/fs-extra diff --git a/lib/readLines.js b/lib/readLines.js index 4178147..073b85c 100644 --- a/lib/readLines.js +++ b/lib/readLines.js @@ -1,7 +1,7 @@ 'use strict'; -var eventStream = require('event-stream'); var fsExtra = require('fs-extra'); var Promise = require('bluebird'); +var readline = require('readline'); module.exports = readLines; @@ -15,13 +15,14 @@ module.exports = readLines; * @private */ function readLines(path, callback) { - return new Promise(function(resolve, reject) { - fsExtra.createReadStream(path) - .on('error', reject) - .on('end', resolve) - .pipe(eventStream.split()) - .pipe(eventStream.mapSync(function (line) { - callback(line); - })); + return new Promise(function (resolve, reject) { + var stream = fsExtra.createReadStream(path); + stream.on('error', reject); + stream.on('end', resolve); + + var lineReader = readline.createInterface({ + input: stream + }); + lineReader.on('line', callback); }); } diff --git a/package.json b/package.json index 62487b3..072e2d1 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "dependencies": { "bluebird": "^3.4.7", "cesium": "^1.31.0", - "event-stream": "^3.3.4", "fs-extra": "^3.0.1", "gltf-pipeline": "^0.1.0-alpha11", "mime": "^1.3.4",