2017-03-13 15:28:51 -04:00
'use strict' ;
2016-07-22 14:09:13 -04:00
var Cesium = require ( 'cesium' ) ;
2015-10-16 17:32:23 -04:00
var path = require ( 'path' ) ;
2017-04-18 11:56:08 -04:00
var PNG = require ( 'pngjs' ) . PNG ;
2017-07-19 17:56:24 -04:00
var getBufferPadded = require ( './getBufferPadded' ) ;
2017-04-10 17:57:56 -04:00
var Material = require ( './Material' ) ;
2016-07-22 14:09:13 -04:00
2017-05-03 17:59:24 -04:00
var CesiumMath = Cesium . Math ;
2017-05-04 15:39:01 -04:00
var defaultValue = Cesium . defaultValue ;
2016-06-09 13:33:08 -04:00
var defined = Cesium . defined ;
var WebGLConstants = Cesium . WebGLConstants ;
2015-10-16 17:32:23 -04:00
module . exports = createGltf ;
2017-03-13 15:28:51 -04:00
/ * *
* Create a glTF from obj data .
*
* @ param { Object } objData Output of obj . js , containing an array of nodes containing geometry information , materials , and images .
2017-04-18 11:56:08 -04:00
* @ param { Object } options An object with the following properties :
2017-05-04 17:58:13 -04:00
* @ param { Boolean } options . packOcclusion Pack the occlusion texture in the red channel of metallic - roughness texture .
* @ param { Boolean } options . metallicRoughness The values in the mtl file are already metallic - roughness PBR values and no conversion step should be applied . Metallic is stored in the Ks and map _Ks slots and roughness is stored in the Ns and map _Ns slots .
* @ param { Boolean } options . specularGlossiness The values in the mtl file are already specular - glossiness PBR values and no conversion step should be applied . Specular is stored in the Ks and map _Ks slots and glossiness is stored in the Ns and map _Ns slots . The glTF will be saved with the KHR _materials _pbrSpecularGlossiness extension .
* @ param { Boolean } options . materialsCommon The glTF will be saved with the KHR _materials _common extension .
2017-07-24 18:21:01 -04:00
* @ param { String } [ options . metallicRoughnessOcclusionTexture ] Path to the metallic - roughness - occlusion texture used by the model , where occlusion is stored in the red channel , roughness is stored in the green channel , and metallic is stored in the blue channel . This may be used instead of setting texture paths in the . mtl file . The model will be saved with a pbrMetallicRoughness material .
* @ param { String } [ options . specularGlossinessTexture ] Path to the specular - glossiness texture used by the model , where specular color is stored in the red , green , and blue channels and specular glossiness is stored in the alpha channel . This may be used instead of setting texture paths in the . mtl file . The model will be saved with a material using the KHR _materials _pbrSpecularGlossiness extension .
* @ param { String } [ options . occlusionTexture ] Path to the occlusion texture used by the model . This may be used instead of setting texture paths in the . mtl file . Ignored if metallicRoughnessOcclusionTexture is also set .
* @ param { String } [ options . normalTexture ] Path to the normal texture used by the model . This may be used instead of setting texture paths in the . mtl file .
* @ param { String } [ options . baseColorTexture ] Path to the baseColor / diffuse texture used by the model . This may be used instead of setting texture paths in the . mtl file .
* @ param { String } [ options . emissiveTexture ] Path to the emissive texture used by the model . This may be used instead of setting texture paths in the . mtl file .
2017-04-18 11:56:08 -04:00
* @ param { Boolean } options . logger A callback function for handling logged messages . Defaults to console . log .
* @ returns { Object } A glTF asset .
2017-03-13 15:28:51 -04:00
*
* @ private
* /
2017-04-18 11:56:08 -04:00
function createGltf ( objData , options ) {
2017-03-13 15:28:51 -04:00
var nodes = objData . nodes ;
var materials = objData . materials ;
var images = objData . images ;
2015-10-16 17:32:23 -04:00
2016-06-09 13:33:08 -04:00
var gltf = {
2017-04-18 11:56:08 -04:00
accessors : [ ] ,
2016-06-09 13:33:08 -04:00
asset : { } ,
2017-04-18 11:56:08 -04:00
buffers : [ ] ,
bufferViews : [ ] ,
2017-05-04 15:39:01 -04:00
extensionsUsed : [ ] ,
extensionsRequired : [ ] ,
2017-04-18 11:56:08 -04:00
images : [ ] ,
materials : [ ] ,
meshes : [ ] ,
nodes : [ ] ,
samplers : [ ] ,
scene : 0 ,
scenes : [ ] ,
textures : [ ]
2016-06-09 13:33:08 -04:00
} ;
gltf . asset = {
2017-03-13 15:28:51 -04:00
generator : 'obj2gltf' ,
2017-04-18 11:56:08 -04:00
version : '2.0'
2016-06-09 13:33:08 -04:00
} ;
2017-04-18 11:56:08 -04:00
gltf . scenes . push ( {
2017-03-13 15:28:51 -04:00
nodes : [ ]
2017-04-18 11:56:08 -04:00
} ) ;
var bufferState = {
2017-07-19 17:56:24 -04:00
positionBuffers : [ ] ,
normalBuffers : [ ] ,
uvBuffers : [ ] ,
2017-04-18 11:56:08 -04:00
indexBuffers : [ ] ,
2017-07-19 17:56:24 -04:00
positionAccessors : [ ] ,
normalAccessors : [ ] ,
uvAccessors : [ ] ,
indexAccessors : [ ]
2016-06-09 13:33:08 -04:00
} ;
2017-04-18 11:56:08 -04:00
var uint32Indices = requiresUint32Indices ( nodes ) ;
2016-06-09 13:33:08 -04:00
2017-04-18 11:56:08 -04:00
var nodesLength = nodes . length ;
for ( var i = 0 ; i < nodesLength ; ++ i ) {
var node = nodes [ i ] ;
var meshes = node . meshes ;
var meshesLength = meshes . length ;
var meshIndex ;
2017-03-13 15:28:51 -04:00
2017-04-18 11:56:08 -04:00
if ( meshesLength === 1 ) {
meshIndex = addMesh ( gltf , materials , images , bufferState , uint32Indices , meshes [ 0 ] , options ) ;
addNode ( gltf , node . name , meshIndex ) ;
} else {
// Add meshes as child nodes
var parentIndex = addNode ( gltf , node . name ) ;
for ( var j = 0 ; j < meshesLength ; ++ j ) {
var mesh = meshes [ j ] ;
meshIndex = addMesh ( gltf , materials , images , bufferState , uint32Indices , mesh , options ) ;
addNode ( gltf , mesh . name , meshIndex , parentIndex ) ;
2017-03-13 15:28:51 -04:00
}
2017-04-18 11:56:08 -04:00
}
2016-06-09 13:33:08 -04:00
}
2015-10-16 17:32:23 -04:00
2017-05-03 17:59:24 -04:00
if ( gltf . images . length > 0 ) {
2017-04-18 11:56:08 -04:00
gltf . samplers . push ( {
2017-03-13 15:28:51 -04:00
magFilter : WebGLConstants . LINEAR ,
2017-07-17 14:38:20 -04:00
minFilter : WebGLConstants . NEAREST _MIPMAP _LINEAR ,
2017-03-13 15:28:51 -04:00
wrapS : WebGLConstants . REPEAT ,
wrapT : WebGLConstants . REPEAT
2017-04-18 11:56:08 -04:00
} ) ;
2017-03-13 15:28:51 -04:00
}
2017-04-18 11:56:08 -04:00
addBuffers ( gltf , bufferState ) ;
return gltf ;
}
2017-07-19 17:56:24 -04:00
function addBufferView ( gltf , buffers , accessors , byteStride , target ) {
var length = buffers . length ;
if ( length === 0 ) {
return ;
}
var bufferViewIndex = gltf . bufferViews . length ;
var previousBufferView = gltf . bufferViews [ bufferViewIndex - 1 ] ;
var byteOffset = defined ( previousBufferView ) ? previousBufferView . byteOffset + previousBufferView . byteLength : 0 ;
var byteLength = 0 ;
for ( var i = 0 ; i < length ; ++ i ) {
var accessor = gltf . accessors [ accessors [ i ] ] ;
accessor . bufferView = bufferViewIndex ;
accessor . byteOffset = byteLength ;
byteLength += buffers [ i ] . length ;
}
gltf . bufferViews . push ( {
name : 'bufferView_' + bufferViewIndex ,
buffer : 0 ,
byteLength : byteLength ,
byteOffset : byteOffset ,
byteStride : byteStride ,
target : target
} ) ;
}
2017-04-18 11:56:08 -04:00
2017-07-19 17:56:24 -04:00
function addBuffers ( gltf , bufferState ) {
// Positions and normals share the same byte stride so they can share the same bufferView
var positionsAndNormalsAccessors = bufferState . positionAccessors . concat ( bufferState . normalAccessors ) ;
var positionsAndNormalsBuffers = bufferState . positionBuffers . concat ( bufferState . normalBuffers ) ;
addBufferView ( gltf , positionsAndNormalsBuffers , positionsAndNormalsAccessors , 12 , WebGLConstants . ARRAY _BUFFER ) ;
addBufferView ( gltf , bufferState . uvBuffers , bufferState . uvAccessors , 8 , WebGLConstants . ARRAY _BUFFER ) ;
addBufferView ( gltf , bufferState . indexBuffers , bufferState . indexAccessors , undefined , WebGLConstants . ELEMENT _ARRAY _BUFFER ) ;
2017-04-18 11:56:08 -04:00
var buffers = [ ] ;
2017-07-19 17:56:24 -04:00
buffers = buffers . concat ( bufferState . positionBuffers , bufferState . normalBuffers , bufferState . uvBuffers , bufferState . indexBuffers ) ;
var buffer = getBufferPadded ( Buffer . concat ( buffers ) ) ;
2017-04-18 11:56:08 -04:00
gltf . buffers . push ( {
2017-07-19 17:56:24 -04:00
name : 'buffer' ,
byteLength : buffer . length ,
2017-04-18 11:56:08 -04:00
extras : {
_obj2gltf : {
source : buffer
2017-03-13 15:28:51 -04:00
}
2015-10-16 17:32:23 -04:00
}
2017-04-18 11:56:08 -04:00
} ) ;
}
2017-07-24 18:21:01 -04:00
function getImage ( images , imagePath , overrideImagePath , options ) {
images = options . overridingImages . concat ( images ) ;
imagePath = defaultValue ( overrideImagePath , imagePath ) ;
2017-05-03 17:59:24 -04:00
var imagesLength = images . length ;
for ( var i = 0 ; i < imagesLength ; ++ i ) {
var image = images [ i ] ;
if ( image . path === imagePath ) {
return image ;
}
2016-06-09 13:33:08 -04:00
}
2017-05-03 17:59:24 -04:00
return undefined ;
2017-04-18 11:56:08 -04:00
}
2015-10-16 17:32:23 -04:00
2017-05-03 17:59:24 -04:00
function getImageName ( image ) {
return path . basename ( image . path , image . extension ) ;
2017-04-18 11:56:08 -04:00
}
2017-03-13 15:28:51 -04:00
2017-05-03 17:59:24 -04:00
function getTextureName ( image ) {
return getImageName ( image ) ;
2017-04-18 11:56:08 -04:00
}
2017-03-13 15:28:51 -04:00
2017-05-03 17:59:24 -04:00
function addTexture ( gltf , image ) {
var imageName = getImageName ( image ) ;
var textureName = getTextureName ( image ) ;
2017-04-18 11:56:08 -04:00
var imageIndex = gltf . images . length ;
var textureIndex = gltf . textures . length ;
2016-06-09 13:33:08 -04:00
2017-04-18 11:56:08 -04:00
gltf . images . push ( {
name : imageName ,
extras : {
_obj2gltf : {
source : image . source ,
extension : image . extension
2017-03-13 15:28:51 -04:00
}
2016-06-09 13:33:08 -04:00
}
2017-04-18 11:56:08 -04:00
} ) ;
gltf . textures . push ( {
name : textureName ,
sampler : 0 ,
source : imageIndex
} ) ;
2017-07-17 17:45:58 -04:00
return {
index : textureIndex
} ;
2017-04-18 11:56:08 -04:00
}
2017-05-03 17:59:24 -04:00
function getTexture ( gltf , image ) {
if ( ! defined ( image ) ) {
return undefined ;
}
var textureIndex ;
var name = getTextureName ( image ) ;
2017-04-18 11:56:08 -04:00
var textures = gltf . textures ;
var length = textures . length ;
for ( var i = 0 ; i < length ; ++ i ) {
if ( textures [ i ] . name === name ) {
2017-05-03 17:59:24 -04:00
textureIndex = i ;
break ;
2017-04-18 11:56:08 -04:00
}
2016-06-09 13:33:08 -04:00
}
2017-04-18 11:56:08 -04:00
if ( ! defined ( textureIndex ) ) {
2017-05-03 17:59:24 -04:00
textureIndex = addTexture ( gltf , image ) ;
2017-04-18 11:56:08 -04:00
}
return textureIndex ;
}
2017-03-13 15:28:51 -04:00
2017-04-18 11:56:08 -04:00
function addColors ( left , right ) {
var red = Math . min ( left [ 0 ] + right [ 0 ] , 1.0 ) ;
var green = Math . min ( left [ 1 ] + right [ 1 ] , 1.0 ) ;
var blue = Math . min ( left [ 2 ] + right [ 2 ] , 1.0 ) ;
return [ red , green , blue ] ;
}
2017-05-03 17:59:24 -04:00
function getEmissiveFactor ( material ) {
// If ambient color is [1, 1, 1] assume it is a multiplier and instead change to [0, 0, 0]
// Then add the ambient color to the emissive color to get the emissive factor.
var ambientColor = material . ambientColor ;
var emissiveColor = material . emissiveColor ;
if ( ambientColor [ 0 ] === 1.0 && ambientColor [ 1 ] === 1.0 && ambientColor [ 2 ] === 1.0 ) {
ambientColor = [ 0.0 , 0.0 , 0.0 , 1.0 ] ;
}
return addColors ( ambientColor , emissiveColor ) ;
}
2017-04-18 11:56:08 -04:00
function resizeChannel ( sourcePixels , sourceWidth , sourceHeight , targetWidth , targetHeight ) {
// Nearest neighbor sampling
var targetPixels = Buffer . alloc ( targetWidth * targetHeight ) ;
var widthRatio = sourceWidth / targetWidth ;
var heightRatio = sourceHeight / targetHeight ;
for ( var y = 0 ; y < targetHeight ; ++ y ) {
for ( var x = 0 ; x < targetWidth ; ++ x ) {
var targetIndex = y * targetWidth + x ;
var sourceY = Math . round ( y * heightRatio ) ;
var sourceX = Math . round ( x * widthRatio ) ;
var sourceIndex = sourceY * sourceWidth + sourceX ;
var sourceValue = sourcePixels . readUInt8 ( sourceIndex ) ;
targetPixels . writeUInt8 ( sourceValue , targetIndex ) ;
}
}
return targetPixels ;
}
2017-05-03 17:59:24 -04:00
function getImageChannel ( image , index , targetWidth , targetHeight ) {
2017-04-18 11:56:08 -04:00
var pixels = image . decoded ; // RGBA
var width = image . width ;
var height = image . height ;
var pixelsLength = width * height ;
2017-05-03 17:59:24 -04:00
var channel = Buffer . alloc ( pixelsLength ) ;
2017-04-18 11:56:08 -04:00
for ( var i = 0 ; i < pixelsLength ; ++ i ) {
2017-05-03 17:59:24 -04:00
var value = pixels . readUInt8 ( i * 4 + index ) ;
channel . writeUInt8 ( value , i ) ;
2017-04-18 11:56:08 -04:00
}
if ( width !== targetWidth || height !== targetHeight ) {
2017-05-03 17:59:24 -04:00
channel = resizeChannel ( channel , width , height , targetWidth , targetHeight ) ;
2017-04-18 11:56:08 -04:00
}
2017-05-03 17:59:24 -04:00
return channel ;
2017-04-18 11:56:08 -04:00
}
function writeChannel ( pixels , channel , index , width , height ) {
var pixelsLength = width * height ;
for ( var i = 0 ; i < pixelsLength ; ++ i ) {
var value = channel . readUInt8 ( i ) ;
pixels . writeUInt8 ( value , i * 4 + index ) ;
}
}
2017-05-03 17:59:24 -04:00
function getMinimumDimensions ( images , options ) {
var i ;
var image ;
var width = Number . POSITIVE _INFINITY ;
var height = Number . POSITIVE _INFINITY ;
var length = images . length ;
for ( i = 0 ; i < length ; ++ i ) {
image = images [ i ] ;
2017-07-17 17:45:58 -04:00
width = Math . min ( image . width , width ) ;
height = Math . min ( image . height , height ) ;
2017-05-03 17:59:24 -04:00
}
for ( i = 0 ; i < length ; ++ i ) {
image = images [ i ] ;
2017-07-17 17:45:58 -04:00
if ( image . width !== width || image . height !== height ) {
options . logger ( 'Image ' + image . path + ' will be scaled from ' + image . width + 'x' + image . height + ' to ' + width + 'x' + height + '.' ) ;
2017-05-03 17:59:24 -04:00
}
2017-04-18 11:56:08 -04:00
}
2017-05-03 17:59:24 -04:00
return [ width , height ] ;
}
function encodePng ( pixels , width , height , inputChannels , outputChannels ) {
var pngInput = {
data : pixels ,
width : width ,
height : height
} ;
// Constants defined by pngjs
var rgbColorType = 2 ;
2017-07-19 17:56:24 -04:00
var rgbaColorType = 6 ;
2017-05-03 17:59:24 -04:00
var colorType = outputChannels === 4 ? rgbaColorType : rgbColorType ;
var inputColorType = inputChannels === 4 ? rgbaColorType : rgbColorType ;
var inputHasAlpha = inputChannels === 4 ;
var pngOptions = {
width : width ,
height : height ,
colorType : colorType ,
inputColorType : inputColorType ,
inputHasAlpha : inputHasAlpha
} ;
return PNG . sync . write ( pngInput , pngOptions ) ;
}
2017-07-24 18:21:01 -04:00
function createMetallicRoughnessTexture ( gltf , metallicImage , roughnessImage , occlusionImage , options ) {
2017-05-03 17:59:24 -04:00
var packMetallic = defined ( metallicImage ) ;
var packRoughness = defined ( roughnessImage ) ;
var packOcclusion = defined ( occlusionImage ) && options . packOcclusion ;
if ( ! packMetallic && ! packRoughness ) {
2017-04-18 11:56:08 -04:00
return undefined ;
}
2017-05-03 17:59:24 -04:00
if ( packMetallic && ! defined ( metallicImage . decoded ) ) {
options . logger ( 'Could not get decoded image data for ' + metallicImage . path + '. The material will be created without a metallicRoughness texture.' ) ;
2017-04-18 11:56:08 -04:00
return undefined ;
}
2017-05-03 17:59:24 -04:00
if ( packRoughness && ! defined ( roughnessImage . decoded ) ) {
options . logger ( 'Could not get decoded image data for ' + roughnessImage . path + '. The material will be created without a metallicRoughness texture.' ) ;
return undefined ;
}
2017-04-18 11:56:08 -04:00
2017-05-03 17:59:24 -04:00
if ( packOcclusion && ! defined ( occlusionImage . decoded ) ) {
options . logger ( 'Could not get decoded image data for ' + occlusionImage . path + '. The occlusion texture will not be packed in the metallicRoughness texture.' ) ;
return undefined ;
2017-04-18 11:56:08 -04:00
}
2017-07-17 17:45:58 -04:00
var packedImages = [ metallicImage , roughnessImage , occlusionImage ] . filter ( function ( image ) {
return defined ( image ) && defined ( image . decoded ) ;
} ) ;
var dimensions = getMinimumDimensions ( packedImages , options ) ;
2017-05-03 17:59:24 -04:00
var width = dimensions [ 0 ] ;
var height = dimensions [ 1 ] ;
2017-04-18 11:56:08 -04:00
var pixelsLength = width * height ;
var pixels = Buffer . alloc ( pixelsLength * 4 , 0xFF ) ; // Initialize with 4 channels, unused channels will be white
2017-05-03 17:59:24 -04:00
if ( packMetallic ) {
2017-04-18 11:56:08 -04:00
// Write into the B channel
2017-05-03 17:59:24 -04:00
var metallicChannel = getImageChannel ( metallicImage , 0 , width , height ) ;
2017-04-18 11:56:08 -04:00
writeChannel ( pixels , metallicChannel , 2 , width , height ) ;
}
2017-05-03 17:59:24 -04:00
if ( packRoughness ) {
2017-04-18 11:56:08 -04:00
// Write into the G channel
2017-05-03 17:59:24 -04:00
var roughnessChannel = getImageChannel ( roughnessImage , 0 , width , height ) ;
2017-04-18 11:56:08 -04:00
writeChannel ( pixels , roughnessChannel , 1 , width , height ) ;
}
2017-05-03 17:59:24 -04:00
if ( packOcclusion ) {
// Write into the R channel
var occlusionChannel = getImageChannel ( occlusionImage , 0 , width , height ) ;
writeChannel ( pixels , occlusionChannel , 0 , width , height ) ;
}
2017-04-18 11:56:08 -04:00
2017-07-24 18:21:01 -04:00
var length = packedImages . length ;
var imageNames = new Array ( length ) ;
for ( var i = 0 ; i < length ; ++ i ) {
var imagePath = packedImages [ i ] . path ;
imageNames [ i ] = path . basename ( imagePath , path . extname ( imagePath ) ) ;
2017-05-03 17:59:24 -04:00
}
2017-07-24 18:21:01 -04:00
var imageName = imageNames . join ( '_' ) ;
2017-05-03 17:59:24 -04:00
var pngSource = encodePng ( pixels , width , height , 4 , 3 ) ;
var image = {
transparent : false ,
source : pngSource ,
path : imageName ,
extension : '.png'
2017-04-18 11:56:08 -04:00
} ;
2017-05-03 17:59:24 -04:00
return addTexture ( gltf , image ) ;
}
2017-07-24 18:21:01 -04:00
function createSpecularGlossinessTexture ( gltf , specularImage , glossinessImage , options ) {
2017-05-03 17:59:24 -04:00
var packSpecular = defined ( specularImage ) ;
var packGlossiness = defined ( glossinessImage ) ;
if ( ! packSpecular && ! packGlossiness ) {
return undefined ;
}
if ( packSpecular && ! defined ( specularImage . decoded ) ) {
options . logger ( 'Could not get decoded image data for ' + specularImage . path + '. The material will be created without a specularGlossiness texture.' ) ;
return undefined ;
}
if ( packGlossiness && ! defined ( glossinessImage . decoded ) ) {
options . logger ( 'Could not get decoded image data for ' + glossinessImage . path + '. The material will be created without a specularGlossiness texture.' ) ;
return undefined ;
}
2017-07-17 17:45:58 -04:00
var packedImages = [ specularImage , glossinessImage ] . filter ( function ( image ) {
return defined ( image ) && defined ( image . decoded ) ;
} ) ;
var dimensions = getMinimumDimensions ( packedImages , options ) ;
2017-05-03 17:59:24 -04:00
var width = dimensions [ 0 ] ;
var height = dimensions [ 1 ] ;
var pixelsLength = width * height ;
var pixels = Buffer . alloc ( pixelsLength * 4 , 0xFF ) ; // Initialize with 4 channels, unused channels will be white
if ( packSpecular ) {
// Write into the R, G, B channels
var redChannel = getImageChannel ( specularImage , 0 , width , height ) ;
var greenChannel = getImageChannel ( specularImage , 1 , width , height ) ;
var blueChannel = getImageChannel ( specularImage , 2 , width , height ) ;
writeChannel ( pixels , redChannel , 0 , width , height ) ;
writeChannel ( pixels , greenChannel , 1 , width , height ) ;
writeChannel ( pixels , blueChannel , 2 , width , height ) ;
}
if ( packGlossiness ) {
// Write into the A channel
var glossinessChannel = getImageChannel ( glossinessImage , 0 , width , height ) ;
writeChannel ( pixels , glossinessChannel , 3 , width , height ) ;
}
2017-07-24 18:21:01 -04:00
var length = packedImages . length ;
var imageNames = new Array ( length ) ;
for ( var i = 0 ; i < length ; ++ i ) {
var imagePath = packedImages [ i ] . path ;
imageNames [ i ] = path . basename ( imagePath , path . extname ( imagePath ) ) ;
}
var imageName = imageNames . join ( '_' ) ;
2017-05-03 17:59:24 -04:00
var pngSource = encodePng ( pixels , width , height , 4 , 4 ) ;
2017-04-18 11:56:08 -04:00
var image = {
transparent : false ,
2017-05-03 17:59:24 -04:00
source : pngSource ,
path : imageName ,
2017-04-18 11:56:08 -04:00
extension : '.png'
} ;
2017-05-03 17:59:24 -04:00
return addTexture ( gltf , image ) ;
2017-04-18 11:56:08 -04:00
}
2017-05-03 17:59:24 -04:00
function createSpecularGlossinessMaterial ( gltf , images , material , options ) {
var materialName = material . name ;
2017-07-24 18:21:01 -04:00
// The texture paths supplied in the .mtl may be overriden by the texture paths supplied in options
var emissiveImage = getImage ( images , material . emissiveTexture , options . emissiveTexture , options ) ;
var normalImage = getImage ( images , material . normalTexture , options . normalTexture , options ) ;
var occlusionImage = getImage ( images , material . ambientTexture , options . occlusionTexture , options ) ;
var diffuseImage = getImage ( images , material . diffuseTexture , options . baseColorTexture , options ) ;
var specularImage = getImage ( images , material . specularTexture , options . specularGlossinessTexture , options ) ;
var glossinessImage = getImage ( images , material . specularShininessTexture , options . specularGlossinessTexture , options ) ;
2017-05-03 17:59:24 -04:00
var emissiveTexture = getTexture ( gltf , emissiveImage ) ;
var normalTexture = getTexture ( gltf , normalImage ) ;
var occlusionTexture = getTexture ( gltf , occlusionImage ) ;
var diffuseTexture = getTexture ( gltf , diffuseImage ) ;
2017-07-24 18:21:01 -04:00
var specularGlossinessTexture ;
if ( defined ( options . specularGlossinessTexture ) ) {
specularGlossinessTexture = getTexture ( gltf , specularImage ) ;
} else {
specularGlossinessTexture = createSpecularGlossinessTexture ( gltf , specularImage , glossinessImage , options ) ;
}
2017-05-03 17:59:24 -04:00
var emissiveFactor = getEmissiveFactor ( material ) ;
var diffuseFactor = material . diffuseColor ;
2017-07-17 17:45:58 -04:00
var specularFactor = material . specularColor . slice ( 0 , 3 ) ;
2017-05-03 17:59:24 -04:00
var glossinessFactor = material . specularShininess ;
if ( defined ( emissiveTexture ) ) {
emissiveFactor = [ 1.0 , 1.0 , 1.0 ] ;
}
if ( defined ( diffuseTexture ) ) {
diffuseFactor = [ 1.0 , 1.0 , 1.0 , 1.0 ] ;
}
if ( defined ( specularImage ) ) {
2017-07-17 17:45:58 -04:00
specularFactor = [ 1.0 , 1.0 , 1.0 ] ;
2017-05-03 17:59:24 -04:00
}
if ( defined ( glossinessImage ) ) {
glossinessFactor = 1.0 ;
}
var alpha = material . alpha ;
diffuseFactor [ 3 ] = alpha ;
var transparent = alpha < 1.0 ;
if ( defined ( diffuseImage ) ) {
2017-07-17 17:45:58 -04:00
transparent = transparent || diffuseImage . transparent ;
2017-05-03 17:59:24 -04:00
}
var doubleSided = transparent ;
var alphaMode = transparent ? 'BLEND' : 'OPAQUE' ;
2017-05-04 15:39:01 -04:00
gltf . extensionsUsed . push ( 'KHR_materials_pbrSpecularGlossiness' ) ;
gltf . extensionsRequired . push ( 'KHR_materials_pbrSpecularGlossiness' ) ;
return {
2017-05-03 17:59:24 -04:00
name : materialName ,
extensions : {
KHR _materials _pbrSpecularGlossiness : {
diffuseTexture : diffuseTexture ,
specularGlossinessTexture : specularGlossinessTexture ,
diffuseFactor : diffuseFactor ,
specularFactor : specularFactor ,
glossinessFactor : glossinessFactor
}
} ,
emissiveTexture : emissiveTexture ,
normalTexture : normalTexture ,
occlusionTexture : occlusionTexture ,
emissiveFactor : emissiveFactor ,
alphaMode : alphaMode ,
doubleSided : doubleSided
} ;
}
2017-04-18 11:56:08 -04:00
2017-05-03 17:59:24 -04:00
function createMetallicRoughnessMaterial ( gltf , images , material , options ) {
var materialName = material . name ;
2017-04-18 11:56:08 -04:00
2017-07-24 18:21:01 -04:00
// The texture paths supplied in the .mtl may be overriden by the texture paths supplied in options
var emissiveImage = getImage ( images , material . emissiveTexture , options . emissiveTexture , options ) ;
var normalImage = getImage ( images , material . normalTexture , options . normalTexture , options ) ;
var occlusionImage = getImage ( images , material . ambientTexture , options . metallicRoughnessOcclusionTexture , options ) ;
var baseColorImage = getImage ( images , material . diffuseTexture , options . baseColorTexture , options ) ;
var metallicImage = getImage ( images , material . specularTexture , options . metallicRoughnessOcclusionTexture , options ) ;
var roughnessImage = getImage ( images , material . specularShininessTexture , options . metallicRoughnessOcclusionTexture , options ) ;
2017-04-18 11:56:08 -04:00
2017-05-03 17:59:24 -04:00
var emissiveTexture = getTexture ( gltf , emissiveImage ) ;
var normalTexture = getTexture ( gltf , normalImage ) ;
var baseColorTexture = getTexture ( gltf , baseColorImage ) ;
2017-07-24 18:21:01 -04:00
var metallicRoughnessTexture ;
if ( defined ( options . metallicRoughnessOcclusionTexture ) ) {
metallicRoughnessTexture = getTexture ( gltf , metallicImage ) ;
} else {
metallicRoughnessTexture = createMetallicRoughnessTexture ( gltf , metallicImage , roughnessImage , occlusionImage , options ) ;
}
var packOcclusion = defined ( occlusionImage ) && options . packOcclusion || defined ( options . metallicRoughnessOcclusionTexture ) ;
2017-05-03 17:59:24 -04:00
var occlusionTexture = packOcclusion ? metallicRoughnessTexture : getTexture ( gltf , occlusionImage ) ;
var emissiveFactor = getEmissiveFactor ( material ) ;
var baseColorFactor = material . diffuseColor ;
var metallicFactor = material . specularColor [ 0 ] ;
var roughnessFactor = material . specularShininess ;
2017-04-18 11:56:08 -04:00
2017-05-03 17:59:24 -04:00
if ( defined ( emissiveTexture ) ) {
emissiveFactor = [ 1.0 , 1.0 , 1.0 ] ;
2017-04-18 11:56:08 -04:00
}
2017-05-03 17:59:24 -04:00
if ( defined ( baseColorTexture ) ) {
baseColorFactor = [ 1.0 , 1.0 , 1.0 , 1.0 ] ;
2017-04-18 11:56:08 -04:00
}
2017-05-03 17:59:24 -04:00
if ( defined ( metallicImage ) ) {
metallicFactor = 1.0 ;
2017-04-18 11:56:08 -04:00
}
2017-05-03 17:59:24 -04:00
if ( defined ( roughnessImage ) ) {
roughnessFactor = 1.0 ;
2017-04-18 11:56:08 -04:00
}
var alpha = material . alpha ;
baseColorFactor [ 3 ] = alpha ;
var transparent = alpha < 1.0 ;
2017-05-03 17:59:24 -04:00
if ( defined ( baseColorImage ) ) {
2017-07-17 17:45:58 -04:00
transparent = transparent || baseColorImage . transparent ;
2017-04-18 11:56:08 -04:00
}
var doubleSided = transparent ;
var alphaMode = transparent ? 'BLEND' : 'OPAQUE' ;
2017-05-04 15:39:01 -04:00
return {
2017-05-03 17:59:24 -04:00
name : materialName ,
2017-04-18 11:56:08 -04:00
pbrMetallicRoughness : {
baseColorTexture : baseColorTexture ,
2017-05-03 17:59:24 -04:00
metallicRoughnessTexture : metallicRoughnessTexture ,
2017-04-18 11:56:08 -04:00
baseColorFactor : baseColorFactor ,
metallicFactor : metallicFactor ,
2017-05-03 17:59:24 -04:00
roughnessFactor : roughnessFactor
2017-04-18 11:56:08 -04:00
} ,
emissiveTexture : emissiveTexture ,
2017-05-03 17:59:24 -04:00
normalTexture : normalTexture ,
occlusionTexture : occlusionTexture ,
2017-04-18 11:56:08 -04:00
emissiveFactor : emissiveFactor ,
alphaMode : alphaMode ,
2017-05-03 17:59:24 -04:00
doubleSided : doubleSided
2017-03-13 15:28:51 -04:00
} ;
2017-05-03 17:59:24 -04:00
}
function convertTraditionalToMetallicRoughness ( material ) {
// Translate the blinn-phong model to the pbr metallic-roughness model
// Roughness factor is a combination of specular intensity and shininess
// Metallic factor is 0.0
2017-05-04 15:39:01 -04:00
// This does not convert textures
2017-05-03 17:59:24 -04:00
var specularIntensity = material . specularColor [ 0 ] ;
var specularShininess = material . specularShininess ;
// Transform from 0-1000 range to 0-1 range. Then invert.
var roughnessFactor = specularShininess ;
roughnessFactor = roughnessFactor / 1000.0 ;
roughnessFactor = 1.0 - roughnessFactor ;
roughnessFactor = CesiumMath . clamp ( roughnessFactor , 0.0 , 1.0 ) ;
// Low specular intensity values should produce a rough material even if shininess is high.
if ( specularIntensity < 0.1 ) {
2017-05-04 17:58:13 -04:00
roughnessFactor *= ( 1.0 - specularIntensity ) ;
2017-05-03 17:59:24 -04:00
}
var metallicFactor = 0.0 ;
2017-05-04 17:58:13 -04:00
material . specularTexture = undefined ; // For now just ignore the specular texture
2017-05-03 17:59:24 -04:00
material . specularColor = [ metallicFactor , metallicFactor , metallicFactor , 1.0 ] ;
2017-05-04 17:58:13 -04:00
material . specularShininess = roughnessFactor ;
2017-05-03 17:59:24 -04:00
}
2017-07-24 18:21:01 -04:00
function createMaterialsCommonMaterial ( gltf , images , material , hasNormals , options ) {
2017-05-04 15:39:01 -04:00
var materialName = material . name ;
2017-07-24 18:21:01 -04:00
var ambientImage = getImage ( images , material . ambientTexture , undefined , options ) ;
var diffuseImage = getImage ( images , material . diffuseTexture , undefined , options ) ;
var emissiveImage = getImage ( images , material . emissiveTexture , undefined , options ) ;
var specularImage = getImage ( images , material . specularTexture , undefined , options ) ;
2017-05-04 15:39:01 -04:00
var ambient = defaultValue ( getTexture ( gltf , ambientImage ) , material . ambientColor ) ;
var diffuse = defaultValue ( getTexture ( gltf , diffuseImage ) , material . diffuseColor ) ;
var emission = defaultValue ( getTexture ( gltf , emissiveImage ) , material . emissiveColor ) ;
var specular = defaultValue ( getTexture ( gltf , specularImage ) , material . specularColor ) ;
var alpha = material . alpha ;
var shininess = material . specularShininess ;
var hasSpecular = ( shininess > 0.0 ) && ( specular [ 0 ] > 0.0 || specular [ 1 ] > 0.0 || specular [ 2 ] > 0.0 ) ;
var transparent ;
var transparency = 1.0 ;
if ( defined ( diffuseImage ) ) {
transparency = alpha ;
transparent = diffuseImage . transparent || ( transparency < 1.0 ) ;
} else {
diffuse [ 3 ] = alpha ;
transparent = alpha < 1.0 ;
}
if ( ! defined ( ambientImage ) ) {
// 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 ;
2017-07-19 13:23:06 -04:00
if ( ! hasNormals ) {
2017-05-04 15:39:01 -04:00
// Constant technique only factors in ambient and emission sources - set emission to diffuse
emission = diffuse ;
}
var technique = hasNormals ? ( hasSpecular ? 'PHONG' : 'LAMBERT' ) : 'CONSTANT' ;
gltf . extensionsUsed . push ( 'KHR_materials_common' ) ;
gltf . extensionsRequired . push ( 'KHR_materials_common' ) ;
return {
name : materialName ,
extensions : {
KHR _materials _common : {
technique : technique ,
transparent : transparent ,
doubleSided : doubleSided ,
values : {
ambient : ambient ,
diffuse : diffuse ,
emission : emission ,
specular : specular ,
shininess : shininess ,
transparency : transparency ,
transparent : transparent ,
doubleSided : doubleSided
}
}
}
} ;
}
function addMaterial ( gltf , images , material , hasNormals , options ) {
2017-05-03 17:59:24 -04:00
var gltfMaterial ;
2017-05-04 15:39:01 -04:00
if ( options . specularGlossiness ) {
2017-05-03 17:59:24 -04:00
gltfMaterial = createSpecularGlossinessMaterial ( gltf , images , material , options ) ;
2017-05-04 15:39:01 -04:00
} else if ( options . metallicRoughness ) {
2017-05-03 17:59:24 -04:00
gltfMaterial = createMetallicRoughnessMaterial ( gltf , images , material , options ) ;
2017-05-04 15:39:01 -04:00
} else if ( options . materialsCommon ) {
2017-07-24 18:21:01 -04:00
gltfMaterial = createMaterialsCommonMaterial ( gltf , images , material , hasNormals , options ) ;
2017-05-03 17:59:24 -04:00
} else {
convertTraditionalToMetallicRoughness ( material ) ;
gltfMaterial = createMetallicRoughnessMaterial ( gltf , images , material , options ) ;
}
2017-04-18 11:56:08 -04:00
var materialIndex = gltf . materials . length ;
gltf . materials . push ( gltfMaterial ) ;
return materialIndex ;
}
2017-05-04 15:39:01 -04:00
function getMaterial ( gltf , materials , images , materialName , hasNormals , options ) {
2017-04-18 11:56:08 -04:00
if ( ! defined ( materialName ) ) {
// Create a default material if the primitive does not specify one
materialName = 'default' ;
}
2017-05-04 17:58:13 -04:00
var i ;
2017-05-03 17:59:24 -04:00
var material ;
var materialsLength = materials . length ;
2017-05-04 17:58:13 -04:00
for ( i = 0 ; i < materialsLength ; ++ i ) {
2017-05-03 17:59:24 -04:00
if ( materials [ i ] . name === materialName ) {
material = materials [ i ] ;
2017-07-17 17:45:58 -04:00
break ;
2017-04-18 11:56:08 -04:00
}
}
2017-05-03 17:59:24 -04:00
if ( ! defined ( material ) ) {
material = new Material ( ) ;
material . name = materialName ;
}
2017-05-04 17:58:13 -04:00
var materialIndex ;
materialsLength = gltf . materials . length ;
for ( i = 0 ; i < materialsLength ; ++ i ) {
if ( gltf . materials [ i ] . name === materialName ) {
materialIndex = i ;
break ;
}
}
2017-05-03 17:59:24 -04:00
2017-04-18 11:56:08 -04:00
if ( ! defined ( materialIndex ) ) {
2017-05-04 15:39:01 -04:00
materialIndex = addMaterial ( gltf , images , material , hasNormals , options ) ;
2017-04-18 11:56:08 -04:00
}
return materialIndex ;
}
2017-07-19 17:56:24 -04:00
function addVertexAttribute ( gltf , array , components , name ) {
2017-04-18 11:56:08 -04:00
var count = array . length / components ;
var minMax = array . getMinMax ( components ) ;
var type = ( components === 3 ? 'VEC3' : 'VEC2' ) ;
var accessor = {
2017-05-04 17:58:13 -04:00
name : name ,
2017-04-18 11:56:08 -04:00
componentType : WebGLConstants . FLOAT ,
count : count ,
min : minMax . min ,
max : minMax . max ,
type : type
2017-03-13 15:28:51 -04:00
} ;
2017-04-18 11:56:08 -04:00
var accessorIndex = gltf . accessors . length ;
gltf . accessors . push ( accessor ) ;
return accessorIndex ;
}
2017-07-19 17:56:24 -04:00
function addIndexArray ( gltf , array , uint32Indices , name ) {
2017-04-18 11:56:08 -04:00
var componentType = uint32Indices ? WebGLConstants . UNSIGNED _INT : WebGLConstants . UNSIGNED _SHORT ;
var count = array . length ;
var minMax = array . getMinMax ( 1 ) ;
var accessor = {
2017-05-04 17:58:13 -04:00
name : name ,
2017-04-18 11:56:08 -04:00
componentType : componentType ,
count : count ,
min : minMax . min ,
max : minMax . max ,
type : 'SCALAR'
2017-03-13 15:28:51 -04:00
} ;
2017-04-18 11:56:08 -04:00
var accessorIndex = gltf . accessors . length ;
gltf . accessors . push ( accessor ) ;
return accessorIndex ;
}
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 ;
}
function addMesh ( gltf , materials , images , bufferState , uint32Indices , mesh , options ) {
var hasPositions = mesh . positions . length > 0 ;
var hasNormals = mesh . normals . length > 0 ;
var hasUVs = mesh . uvs . length > 0 ;
2017-07-19 17:56:24 -04:00
var accessorIndex ;
2017-04-18 11:56:08 -04:00
var attributes = { } ;
if ( hasPositions ) {
2017-07-19 17:56:24 -04:00
accessorIndex = addVertexAttribute ( gltf , mesh . positions , 3 , mesh . name + '_positions' ) ;
attributes . POSITION = accessorIndex ;
bufferState . positionBuffers . push ( mesh . positions . toFloatBuffer ( ) ) ;
bufferState . positionAccessors . push ( accessorIndex ) ;
2017-04-18 11:56:08 -04:00
}
if ( hasNormals ) {
2017-07-19 17:56:24 -04:00
accessorIndex = addVertexAttribute ( gltf , mesh . normals , 3 , mesh . name + '_normals' ) ;
attributes . NORMAL = accessorIndex ;
bufferState . normalBuffers . push ( mesh . normals . toFloatBuffer ( ) ) ;
bufferState . normalAccessors . push ( accessorIndex ) ;
2017-04-18 11:56:08 -04:00
}
if ( hasUVs ) {
2017-07-19 17:56:24 -04:00
accessorIndex = addVertexAttribute ( gltf , mesh . uvs , 2 , mesh . name + '_texcoords' ) ;
attributes . TEXCOORD _0 = accessorIndex ;
bufferState . uvBuffers . push ( mesh . uvs . toFloatBuffer ( ) ) ;
bufferState . uvAccessors . push ( accessorIndex ) ;
2017-04-18 11:56:08 -04:00
}
// Unload resources
mesh . positions = undefined ;
mesh . normals = undefined ;
mesh . uvs = undefined ;
var gltfPrimitives = [ ] ;
var primitives = mesh . primitives ;
var primitivesLength = primitives . length ;
for ( var i = 0 ; i < primitivesLength ; ++ i ) {
var primitive = primitives [ i ] ;
2017-07-19 17:56:24 -04:00
var indexAccessorIndex = addIndexArray ( gltf , primitive . indices , uint32Indices , mesh . name + '_' + i + '_indices' ) ;
var indexBuffer = uint32Indices ? primitive . indices . toUint32Buffer ( ) : primitive . indices . toUint16Buffer ( ) ;
bufferState . indexBuffers . push ( indexBuffer ) ;
bufferState . indexAccessors . push ( indexAccessorIndex ) ;
2017-04-18 11:56:08 -04:00
primitive . indices = undefined ; // Unload resources
2017-05-04 15:39:01 -04:00
var materialIndex = getMaterial ( gltf , materials , images , primitive . material , hasNormals , options ) ;
2017-04-18 11:56:08 -04:00
gltfPrimitives . push ( {
attributes : attributes ,
indices : indexAccessorIndex ,
material : materialIndex ,
mode : WebGLConstants . TRIANGLES
} ) ;
}
var gltfMesh = {
name : mesh . name ,
primitives : gltfPrimitives
} ;
var meshIndex = gltf . meshes . length ;
gltf . meshes . push ( gltfMesh ) ;
return meshIndex ;
}
function addNode ( gltf , name , meshIndex , parentIndex ) {
var node = {
name : name ,
mesh : meshIndex
} ;
var nodeIndex = gltf . nodes . length ;
gltf . nodes . push ( node ) ;
if ( defined ( parentIndex ) ) {
var parentNode = gltf . nodes [ parentIndex ] ;
if ( ! defined ( parentNode . children ) ) {
parentNode . children = [ ] ;
}
parentNode . children . push ( nodeIndex ) ;
} else {
gltf . scenes [ gltf . scene ] . nodes . push ( nodeIndex ) ;
}
return nodeIndex ;
2015-10-16 17:32:23 -04:00
}