2017-03-17 15:44:01 -04:00
'use strict' ;
2019-02-05 20:59:09 -05:00
const Cesium = require ( 'cesium' ) ;
const mime = require ( 'mime' ) ;
const PNG = require ( 'pngjs' ) . PNG ;
const Promise = require ( 'bluebird' ) ;
const getBufferPadded = require ( './getBufferPadded' ) ;
const gltfToGlb = require ( './gltfToGlb' ) ;
2017-03-17 15:44:01 -04:00
2019-02-05 20:59:09 -05:00
const defined = Cesium . defined ;
const RuntimeError = Cesium . RuntimeError ;
2017-04-10 17:57:56 -04:00
2017-07-29 13:23:33 -04:00
module . exports = writeGltf ;
2017-03-17 15:44:01 -04:00
/ * *
* Write glTF resources as embedded data uris or external files .
*
* @ param { Object } gltf The glTF asset .
2017-07-29 13:23:33 -04:00
* @ param { Object } options The options object passed along from lib / obj2gltf . js
* @ returns { Promise } A promise that resolves to the glTF JSON or glb buffer .
2017-03-17 15:44:01 -04:00
*
* @ private
* /
2017-07-29 13:23:33 -04:00
function writeGltf ( gltf , options ) {
return encodeTextures ( gltf )
2017-07-27 11:23:12 -04:00
. then ( function ( ) {
2019-02-05 20:59:09 -05:00
const binary = options . binary ;
const separate = options . separate ;
const separateTextures = options . separateTextures ;
2017-07-27 11:23:12 -04:00
2019-02-05 20:59:09 -05:00
const promises = [ ] ;
2017-07-27 11:23:12 -04:00
if ( separateTextures ) {
2017-07-29 13:23:33 -04:00
promises . push ( writeSeparateTextures ( gltf , options ) ) ;
2017-07-27 11:23:12 -04:00
} else {
writeEmbeddedTextures ( gltf ) ;
}
if ( separate ) {
2017-07-29 13:23:33 -04:00
promises . push ( writeSeparateBuffer ( gltf , options ) ) ;
} else if ( ! binary ) {
2017-07-27 11:23:12 -04:00
writeEmbeddedBuffer ( gltf ) ;
}
2019-02-05 20:59:09 -05:00
const binaryBuffer = gltf . buffers [ 0 ] . extras . _obj2gltf . source ;
2017-07-29 13:23:33 -04:00
2017-07-27 11:23:12 -04:00
return Promise . all ( promises )
. then ( function ( ) {
deleteExtras ( gltf ) ;
2017-07-29 13:23:33 -04:00
removeEmpty ( gltf ) ;
if ( binary ) {
return gltfToGlb ( gltf , binaryBuffer ) ;
}
2017-07-27 11:23:12 -04:00
return gltf ;
} ) ;
} ) ;
}
2017-03-17 15:44:01 -04:00
2017-07-29 13:23:33 -04:00
function encodePng ( texture ) {
2017-07-27 11:23:12 -04:00
// Constants defined by pngjs
2019-02-05 20:59:09 -05:00
const rgbColorType = 2 ;
const rgbaColorType = 6 ;
2017-07-27 11:23:12 -04:00
2019-02-05 20:59:09 -05:00
const png = new PNG ( {
2017-07-29 13:23:33 -04:00
width : texture . width ,
height : texture . height ,
colorType : texture . transparent ? rgbaColorType : rgbColorType ,
2017-07-27 11:23:12 -04:00
inputColorType : rgbaColorType ,
inputHasAlpha : true
} ) ;
2017-07-29 13:23:33 -04:00
png . data = texture . pixels ;
2017-07-27 11:23:12 -04:00
return new Promise ( function ( resolve , reject ) {
2019-02-05 20:59:09 -05:00
const chunks = [ ] ;
const stream = png . pack ( ) ;
2017-07-27 11:23:12 -04:00
stream . on ( 'data' , function ( chunk ) {
chunks . push ( chunk ) ;
} ) ;
stream . on ( 'end' , function ( ) {
resolve ( Buffer . concat ( chunks ) ) ;
} ) ;
stream . on ( 'error' , reject ) ;
} ) ;
}
2017-04-20 10:07:01 -04:00
2017-07-29 13:23:33 -04:00
function encodeTexture ( texture ) {
if ( ! defined ( texture . source ) && defined ( texture . pixels ) && texture . extension === '.png' ) {
return encodePng ( texture )
2017-07-27 11:23:12 -04:00
. then ( function ( encoded ) {
2017-07-29 13:23:33 -04:00
texture . source = encoded ;
2017-07-27 11:23:12 -04:00
} ) ;
2017-03-17 15:44:01 -04:00
}
2017-07-27 11:23:12 -04:00
}
2017-03-17 15:44:01 -04:00
2017-07-29 13:23:33 -04:00
function encodeTextures ( gltf ) {
// Dynamically generated PBR textures need to be encoded to png prior to being saved
2019-02-05 20:59:09 -05:00
const encodePromises = [ ] ;
const images = gltf . images ;
const length = images . length ;
for ( let i = 0 ; i < length ; ++ i ) {
2017-07-29 13:23:33 -04:00
encodePromises . push ( encodeTexture ( images [ i ] . extras . _obj2gltf ) ) ;
2017-03-17 15:44:01 -04:00
}
2017-07-27 11:23:12 -04:00
return Promise . all ( encodePromises ) ;
2017-03-17 15:44:01 -04:00
}
function deleteExtras ( gltf ) {
2019-02-05 20:59:09 -05:00
const buffer = gltf . buffers [ 0 ] ;
2017-03-17 15:44:01 -04:00
delete buffer . extras ;
2019-02-05 20:59:09 -05:00
const images = gltf . images ;
const imagesLength = images . length ;
for ( let i = 0 ; i < imagesLength ; ++ i ) {
2017-04-18 11:56:08 -04:00
delete images [ i ] . extras ;
}
2017-03-17 15:44:01 -04:00
}
2017-05-04 17:58:13 -04:00
function removeEmpty ( json ) {
Object . keys ( json ) . forEach ( function ( key ) {
if ( ! defined ( json [ key ] ) || ( Array . isArray ( json [ key ] ) && json [ key ] . length === 0 ) ) {
delete json [ key ] ; // Delete values that are undefined or []
} else if ( typeof json [ key ] === 'object' ) {
removeEmpty ( json [ key ] ) ;
2017-05-03 17:59:24 -04:00
}
2017-05-04 17:58:13 -04:00
} ) ;
}
2017-07-29 13:23:33 -04:00
function writeSeparateBuffer ( gltf , options ) {
2019-02-05 20:59:09 -05:00
const buffer = gltf . buffers [ 0 ] ;
const source = buffer . extras . _obj2gltf . source ;
const bufferUri = buffer . name + '.bin' ;
2017-03-17 15:44:01 -04:00
buffer . uri = bufferUri ;
2017-07-29 13:23:33 -04:00
return options . writer ( bufferUri , source ) ;
2017-03-17 15:44:01 -04:00
}
2017-07-29 13:23:33 -04:00
function writeSeparateTextures ( gltf , options ) {
2019-02-05 20:59:09 -05:00
const images = gltf . images ;
2017-04-18 11:56:08 -04:00
return Promise . map ( images , function ( image ) {
2019-02-05 20:59:09 -05:00
const texture = image . extras . _obj2gltf ;
const imageUri = image . name + texture . extension ;
2017-04-10 17:57:56 -04:00
image . uri = imageUri ;
2017-07-29 13:23:33 -04:00
return options . writer ( imageUri , texture . source ) ;
2017-04-10 17:57:56 -04:00
} , { concurrency : 10 } ) ;
2017-03-17 15:44:01 -04:00
}
function writeEmbeddedBuffer ( gltf ) {
2019-02-05 20:59:09 -05:00
const buffer = gltf . buffers [ 0 ] ;
const source = buffer . extras . _obj2gltf . source ;
2017-07-29 13:23:33 -04:00
2019-02-05 18:41:32 -05:00
try {
buffer . uri = 'data:application/octet-stream;base64,' + source . toString ( 'base64' ) ;
} catch ( error ) {
// In some versions of Node a buffer larger than ~192MB cannot be base64 encoded due to string size limitations. Source: https://stackoverflow.com/posts/47781288/revisions
2017-07-29 13:23:33 -04:00
throw new RuntimeError ( 'Buffer is too large to embed in the glTF. Use the --separate flag instead.' ) ;
}
2017-03-17 15:44:01 -04:00
}
function writeEmbeddedTextures ( gltf ) {
2019-02-05 20:59:09 -05:00
const buffer = gltf . buffers [ 0 ] ;
const bufferExtras = buffer . extras . _obj2gltf ;
const bufferSource = bufferExtras . source ;
const images = gltf . images ;
const imagesLength = images . length ;
const sources = [ bufferSource ] ;
let byteOffset = bufferSource . length ;
for ( let i = 0 ; i < imagesLength ; ++ i ) {
const image = images [ i ] ;
const texture = image . extras . _obj2gltf ;
const textureSource = texture . source ;
const textureByteLength = textureSource . length ;
2017-07-19 17:56:24 -04:00
2017-11-06 09:42:37 -05:00
image . mimeType = mime . getType ( texture . extension ) ;
2017-07-19 17:56:24 -04:00
image . bufferView = gltf . bufferViews . length ;
2017-07-19 13:23:06 -04:00
gltf . bufferViews . push ( {
buffer : 0 ,
2017-07-19 17:56:24 -04:00
byteOffset : byteOffset ,
2017-07-29 13:23:33 -04:00
byteLength : textureByteLength
2017-07-19 13:23:06 -04:00
} ) ;
2017-07-29 13:23:33 -04:00
byteOffset += textureByteLength ;
sources . push ( textureSource ) ;
2017-03-17 15:44:01 -04:00
}
2017-07-19 17:56:24 -04:00
2019-02-05 20:59:09 -05:00
const source = getBufferPadded ( Buffer . concat ( sources ) ) ;
2017-07-19 17:56:24 -04:00
bufferExtras . source = source ;
buffer . byteLength = source . length ;
2017-03-17 15:44:01 -04:00
}