2017-03-13 15:28:51 -04:00
'use strict' ;
2019-02-05 20:59:09 -05:00
const Cesium = require ( 'cesium' ) ;
const path = require ( 'path' ) ;
const Promise = require ( 'bluebird' ) ;
const loadTexture = require ( './loadTexture' ) ;
const outsideDirectory = require ( './outsideDirectory' ) ;
const readLines = require ( './readLines' ) ;
const Texture = require ( './Texture' ) ;
const CesiumMath = Cesium . Math ;
2019-10-26 19:03:32 -04:00
const clone = Cesium . clone ;
2019-02-05 20:59:09 -05:00
const combine = Cesium . combine ;
const defaultValue = Cesium . defaultValue ;
const defined = Cesium . defined ;
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
module . exports = loadMtl ;
2016-07-22 14:09:13 -04:00
2017-03-13 15:28:51 -04:00
/ * *
2017-07-29 13:23:33 -04:00
* Parse a . mtl file and load textures referenced within . Returns an array of glTF materials with Texture
* objects stored in the texture slots .
2017-08-11 12:01:23 -04:00
* < p >
* Packed PBR textures ( like metallicRoughnessOcclusion and specularGlossiness ) require all input textures to be decoded before hand .
* If a texture is of an unsupported format like . gif or . tga it can ' t be packed and a metallicRoughness texture will not be created .
* Similarly if a texture cannot be found it will be ignored and a default value will be used instead .
* < / p >
2017-03-13 15:28:51 -04:00
*
2017-07-29 13:23:33 -04:00
* @ param { String } mtlPath Path to the . mtl file .
* @ param { Object } options The options object passed along from lib / obj2gltf . js
* @ returns { Promise } A promise resolving to an array of glTF materials with Texture objects stored in the texture slots .
2017-03-13 15:28:51 -04:00
*
* @ private
* /
2017-05-04 17:58:13 -04:00
function loadMtl ( mtlPath , options ) {
2019-02-05 20:59:09 -05:00
let material ;
let values ;
let value ;
const mtlDirectory = path . dirname ( mtlPath ) ;
const materials = [ ] ;
const texturePromiseMap = { } ; // Maps texture paths to load promises so that no texture is loaded twice
const texturePromises = [ ] ;
const overridingTextures = options . overridingTextures ;
const overridingSpecularTexture = defaultValue ( overridingTextures . metallicRoughnessOcclusionTexture , overridingTextures . specularGlossinessTexture ) ;
const overridingSpecularShininessTexture = defaultValue ( overridingTextures . metallicRoughnessOcclusionTexture , overridingTextures . specularGlossinessTexture ) ;
const overridingAmbientTexture = defaultValue ( overridingTextures . metallicRoughnessOcclusionTexture , overridingTextures . occlusionTexture ) ;
const overridingNormalTexture = overridingTextures . normalTexture ;
const overridingDiffuseTexture = overridingTextures . baseColorTexture ;
const overridingEmissiveTexture = overridingTextures . emissiveTexture ;
const overridingAlphaTexture = overridingTextures . alphaTexture ;
2017-07-29 13:23:33 -04:00
// Textures that are packed into PBR textures need to be decoded first
2019-02-05 20:59:09 -05:00
const decodeOptions = {
2017-07-29 13:23:33 -04:00
decode : true
} ;
2019-02-05 20:59:09 -05:00
const diffuseTextureOptions = {
2017-07-29 13:23:33 -04:00
checkTransparency : options . checkTransparency
} ;
2017-03-13 15:28:51 -04:00
2019-02-05 20:59:09 -05:00
const ambientTextureOptions = defined ( overridingAmbientTexture ) ? undefined : ( options . packOcclusion ? decodeOptions : undefined ) ;
const specularTextureOptions = defined ( overridingSpecularTexture ) ? undefined : decodeOptions ;
const specularShinessTextureOptions = defined ( overridingSpecularShininessTexture ) ? undefined : decodeOptions ;
const emissiveTextureOptions = undefined ;
const normalTextureOptions = undefined ;
const alphaTextureOptions = {
2018-01-03 20:41:25 -05:00
decode : true
} ;
2017-07-29 13:23:33 -04:00
function createMaterial ( name ) {
material = new Material ( ) ;
material . name = name ;
material . specularShininess = options . metallicRoughness ? 1.0 : 0.0 ;
2017-11-07 09:20:42 -05:00
material . specularTexture = overridingSpecularTexture ;
material . specularShininessTexture = overridingSpecularShininessTexture ;
material . diffuseTexture = overridingDiffuseTexture ;
material . ambientTexture = overridingAmbientTexture ;
material . normalTexture = overridingNormalTexture ;
material . emissiveTexture = overridingEmissiveTexture ;
2018-01-03 20:41:25 -05:00
material . alphaTexture = overridingAlphaTexture ;
2017-07-29 13:23:33 -04:00
materials . push ( material ) ;
2017-05-04 17:58:13 -04:00
}
2018-08-30 10:42:10 -04:00
function normalizeTexturePath ( texturePath , mtlDirectory ) {
// Removes texture options from texture name
// Assumes no spaces in texture name
2019-02-05 20:59:09 -05:00
const re = /-(bm|t|s|o|blendu|blendv|boost|mm|texres|clamp|imfchan|type)/ ;
2018-08-30 10:42:10 -04:00
if ( re . test ( texturePath ) ) {
texturePath = texturePath . split ( /\s+/ ) . pop ( ) ;
2017-10-10 09:06:45 -04:00
}
2018-08-30 10:42:10 -04:00
texturePath = texturePath . replace ( /\\/g , '/' ) ;
2019-10-26 20:42:12 -04:00
return path . normalize ( path . resolve ( mtlDirectory , texturePath ) ) ;
2017-10-10 07:23:09 -04:00
}
2017-03-13 15:28:51 -04:00
function parseLine ( line ) {
line = line . trim ( ) ;
2019-01-03 09:29:02 -05:00
if ( /^newmtl/i . test ( line ) ) {
2019-02-05 20:59:09 -05:00
const name = line . substring ( 7 ) . trim ( ) ;
2017-07-29 13:23:33 -04:00
createMaterial ( name ) ;
2017-03-13 15:28:51 -04:00
} 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 ( ' ' ) ;
2017-04-18 11:56:08 -04:00
material . emissiveColor = [
2017-03-13 15:28:51 -04:00
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 ( ) ;
2017-09-19 11:59:51 -04:00
material . alpha = correctAlpha ( parseFloat ( value ) ) ;
2017-03-13 15:28:51 -04:00
} else if ( /^Tr /i . test ( line ) ) {
value = line . substring ( 3 ) . trim ( ) ;
2017-09-19 11:59:51 -04:00
material . alpha = correctAlpha ( 1.0 - parseFloat ( value ) ) ;
2017-03-13 15:28:51 -04:00
} else if ( /^map_Ka /i . test ( line ) ) {
2017-07-29 13:23:33 -04:00
if ( ! defined ( overridingAmbientTexture ) ) {
2018-08-30 10:42:10 -04:00
material . ambientTexture = normalizeTexturePath ( line . substring ( 7 ) . trim ( ) , mtlDirectory ) ;
2017-07-29 13:23:33 -04:00
}
2017-03-13 15:28:51 -04:00
} else if ( /^map_Ke /i . test ( line ) ) {
2017-07-29 13:23:33 -04:00
if ( ! defined ( overridingEmissiveTexture ) ) {
2018-08-30 10:42:10 -04:00
material . emissiveTexture = normalizeTexturePath ( line . substring ( 7 ) . trim ( ) , mtlDirectory ) ;
2017-07-29 13:23:33 -04:00
}
2017-03-13 15:28:51 -04:00
} else if ( /^map_Kd /i . test ( line ) ) {
2017-07-29 13:23:33 -04:00
if ( ! defined ( overridingDiffuseTexture ) ) {
2018-08-30 10:42:10 -04:00
material . diffuseTexture = normalizeTexturePath ( line . substring ( 7 ) . trim ( ) , mtlDirectory ) ;
2017-07-29 13:23:33 -04:00
}
2017-03-13 15:28:51 -04:00
} else if ( /^map_Ks /i . test ( line ) ) {
2017-07-29 13:23:33 -04:00
if ( ! defined ( overridingSpecularTexture ) ) {
2018-08-30 10:42:10 -04:00
material . specularTexture = normalizeTexturePath ( line . substring ( 7 ) . trim ( ) , mtlDirectory ) ;
2017-07-29 13:23:33 -04:00
}
2017-03-13 15:28:51 -04:00
} else if ( /^map_Ns /i . test ( line ) ) {
2017-07-29 13:23:33 -04:00
if ( ! defined ( overridingSpecularShininessTexture ) ) {
2018-08-30 10:42:10 -04:00
material . specularShininessTexture = normalizeTexturePath ( line . substring ( 7 ) . trim ( ) , mtlDirectory ) ;
2017-07-29 13:23:33 -04:00
}
2017-03-13 15:28:51 -04:00
} else if ( /^map_Bump /i . test ( line ) ) {
2017-07-29 13:23:33 -04:00
if ( ! defined ( overridingNormalTexture ) ) {
2018-08-30 10:42:10 -04:00
material . normalTexture = normalizeTexturePath ( line . substring ( 9 ) . trim ( ) , mtlDirectory ) ;
2017-07-29 13:23:33 -04:00
}
2018-01-03 20:41:25 -05:00
} else if ( /^map_d /i . test ( line ) ) {
if ( ! defined ( overridingAlphaTexture ) ) {
2018-08-30 10:42:10 -04:00
material . alphaTexture = normalizeTexturePath ( line . substring ( 6 ) . trim ( ) , mtlDirectory ) ;
2018-01-03 20:41:25 -05:00
}
2017-03-13 15:28:51 -04:00
}
}
2015-10-16 17:32:23 -04:00
2017-11-07 09:20:42 -05:00
function loadMaterialTextures ( material ) {
2018-01-03 20:41:25 -05:00
// If an alpha texture is present the diffuse texture needs to be decoded so they can be packed together
2019-02-05 20:59:09 -05:00
const diffuseAlphaTextureOptions = defined ( material . alphaTexture ) ? alphaTextureOptions : diffuseTextureOptions ;
2018-01-03 20:41:25 -05:00
2018-01-19 11:02:14 -05:00
if ( material . diffuseTexture === material . ambientTexture ) {
// OBJ models are often exported with the same texture in the diffuse and ambient slots but this is typically not desirable, particularly
// when saving with PBR materials where the ambient texture is treated as the occlusion texture.
material . ambientTexture = undefined ;
}
2018-01-29 09:28:55 -05:00
2019-10-26 19:03:32 -04:00
const textureNames = [ 'diffuseTexture' , 'ambientTexture' , 'emissiveTexture' , 'specularTexture' , 'specularShininessTexture' , 'normalTexture' , 'alphaTexture' ] ;
const textureOptions = [ diffuseAlphaTextureOptions , ambientTextureOptions , emissiveTextureOptions , specularTextureOptions , specularShinessTextureOptions , normalTextureOptions , alphaTextureOptions ] ;
const sharedOptions = { } ;
textureNames . forEach ( function ( name , index ) {
const texturePath = material [ name ] ;
const originalOptions = textureOptions [ index ] ;
if ( defined ( texturePath ) && defined ( originalOptions ) ) {
if ( ! defined ( sharedOptions [ texturePath ] ) ) {
sharedOptions [ texturePath ] = clone ( originalOptions ) ;
}
const options = sharedOptions [ texturePath ] ;
options . checkTransparency = options . checkTransparency || originalOptions . checkTransparency ;
options . decode = options . decode || originalOptions . decode ;
options . keepSource = options . keepSource || ! originalOptions . decode || ! originalOptions . checkTransparency ;
}
} ) ;
textureNames . forEach ( function ( name ) {
const texturePath = material [ name ] ;
if ( defined ( texturePath ) ) {
loadMaterialTexture ( material , name , sharedOptions [ texturePath ] , mtlDirectory , texturePromiseMap , texturePromises , options ) ;
}
} ) ;
2017-11-07 09:20:42 -05:00
}
2017-03-13 15:28:51 -04:00
return readLines ( mtlPath , parseLine )
. then ( function ( ) {
2019-02-05 20:59:09 -05:00
const length = materials . length ;
for ( let i = 0 ; i < length ; ++ i ) {
2017-11-07 09:20:42 -05:00
loadMaterialTextures ( materials [ i ] ) ;
}
2017-07-29 13:23:33 -04:00
return Promise . all ( texturePromises ) ;
} )
. then ( function ( ) {
return convertMaterials ( materials , options ) ;
2016-07-22 14:09:13 -04:00
} ) ;
2015-10-16 17:32:23 -04:00
}
2017-07-29 13:23:33 -04:00
2017-09-19 11:59:51 -04:00
function correctAlpha ( alpha ) {
// An alpha of 0.0 usually implies a problem in the export, change to 1.0 instead
return alpha === 0.0 ? 1.0 : alpha ;
}
2017-07-29 13:23:33 -04:00
function Material ( ) {
this . name = undefined ;
this . ambientColor = [ 0.0 , 0.0 , 0.0 , 1.0 ] ; // Ka
this . emissiveColor = [ 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 . emissiveTexture = undefined ; // map_Ke
this . diffuseTexture = undefined ; // map_Kd
this . specularTexture = undefined ; // map_Ks
this . specularShininessTexture = undefined ; // map_Ns
this . normalTexture = undefined ; // map_Bump
2018-01-03 20:41:25 -05:00
this . alphaTexture = undefined ; // map_d
2017-07-29 13:23:33 -04:00
}
loadMtl . getDefaultMaterial = function ( options ) {
return convertMaterial ( new Material ( ) , options ) ;
} ;
// Exposed for testing
loadMtl . _createMaterial = function ( materialOptions , options ) {
return convertMaterial ( combine ( materialOptions , new Material ( ) ) , options ) ;
} ;
2017-11-07 09:20:42 -05:00
function loadMaterialTexture ( material , name , textureOptions , mtlDirectory , texturePromiseMap , texturePromises , options ) {
2019-02-05 20:59:09 -05:00
const texturePath = material [ name ] ;
2017-07-29 13:23:33 -04:00
if ( ! defined ( texturePath ) ) {
return ;
}
2017-09-29 13:26:51 -04:00
2019-02-05 20:59:09 -05:00
let texturePromise = texturePromiseMap [ texturePath ] ;
2017-07-29 13:23:33 -04:00
if ( ! defined ( texturePromise ) ) {
2019-02-05 20:59:09 -05:00
const shallowPath = path . join ( mtlDirectory , path . basename ( texturePath ) ) ;
2017-07-29 13:23:33 -04:00
if ( options . secure && outsideDirectory ( texturePath , mtlDirectory ) ) {
2017-11-29 14:21:59 -05:00
// Try looking for the texture in the same directory as the obj
2017-12-21 22:19:52 -05:00
options . logger ( 'Texture file is outside of the mtl directory and the secure flag is true. Attempting to read the texture file from within the obj directory instead.' ) ;
2017-11-29 14:21:59 -05:00
texturePromise = loadTexture ( shallowPath , textureOptions )
2017-12-21 22:19:52 -05:00
. catch ( function ( error ) {
options . logger ( error . message ) ;
options . logger ( 'Could not read texture file at ' + shallowPath + '. This texture will be ignored' ) ;
2017-11-29 14:21:59 -05:00
} ) ;
2017-07-29 13:23:33 -04:00
} else {
texturePromise = loadTexture ( texturePath , textureOptions )
2017-12-21 22:19:52 -05:00
. catch ( function ( error ) {
2017-11-17 15:07:52 -05:00
// Try looking for the texture in the same directory as the obj
2017-12-21 22:19:52 -05:00
options . logger ( error . message ) ;
options . logger ( 'Could not read texture file at ' + texturePath + '. Attempting to read the texture file from within the obj directory instead.' ) ;
2017-11-17 15:07:52 -05:00
return loadTexture ( shallowPath , textureOptions ) ;
} )
2017-12-21 22:19:52 -05:00
. catch ( function ( error ) {
options . logger ( error . message ) ;
options . logger ( 'Could not read texture file at ' + shallowPath + '. This texture will be ignored.' ) ;
2017-07-29 13:23:33 -04:00
} ) ;
}
texturePromiseMap [ texturePath ] = texturePromise ;
}
texturePromises . push ( texturePromise
. then ( function ( texture ) {
material [ name ] = texture ;
} ) ) ;
}
function convertMaterial ( material , options ) {
if ( options . specularGlossiness ) {
return createSpecularGlossinessMaterial ( material , options ) ;
} else if ( options . metallicRoughness ) {
return createMetallicRoughnessMaterial ( material , options ) ;
2018-08-30 16:02:54 -04:00
}
2017-07-29 13:23:33 -04:00
// No material type specified, convert the material to metallic roughness
convertTraditionalToMetallicRoughness ( material ) ;
return createMetallicRoughnessMaterial ( material , options ) ;
}
function convertMaterials ( materials , options ) {
return materials . map ( function ( material ) {
return convertMaterial ( material , options ) ;
} ) ;
}
function resizeChannel ( sourcePixels , sourceWidth , sourceHeight , targetPixels , targetWidth , targetHeight ) {
// Nearest neighbor sampling
2019-02-05 20:59:09 -05:00
const widthRatio = sourceWidth / targetWidth ;
const heightRatio = sourceHeight / targetHeight ;
for ( let y = 0 ; y < targetHeight ; ++ y ) {
for ( let x = 0 ; x < targetWidth ; ++ x ) {
const targetIndex = y * targetWidth + x ;
const sourceY = Math . round ( y * heightRatio ) ;
const sourceX = Math . round ( x * widthRatio ) ;
const sourceIndex = sourceY * sourceWidth + sourceX ;
const sourceValue = sourcePixels . readUInt8 ( sourceIndex ) ;
2017-07-29 13:23:33 -04:00
targetPixels . writeUInt8 ( sourceValue , targetIndex ) ;
}
}
return targetPixels ;
}
2019-02-05 20:59:09 -05:00
let scratchResizeChannel ;
2017-07-29 13:23:33 -04:00
function getTextureChannel ( texture , index , targetWidth , targetHeight , targetChannel ) {
2019-02-05 20:59:09 -05:00
const pixels = texture . pixels ; // RGBA
const sourceWidth = texture . width ;
const sourceHeight = texture . height ;
const sourcePixelsLength = sourceWidth * sourceHeight ;
const targetPixelsLength = targetWidth * targetHeight ;
2017-07-29 13:23:33 -04:00
// Allocate the scratchResizeChannel on demand if the texture needs to be resized
2019-02-05 20:59:09 -05:00
let sourceChannel = targetChannel ;
2017-07-29 13:23:33 -04:00
if ( sourcePixelsLength > targetPixelsLength ) {
if ( ! defined ( scratchResizeChannel ) || ( sourcePixelsLength > scratchResizeChannel . length ) ) {
scratchResizeChannel = Buffer . alloc ( sourcePixelsLength ) ;
}
sourceChannel = scratchResizeChannel ;
}
2019-02-05 20:59:09 -05:00
for ( let i = 0 ; i < sourcePixelsLength ; ++ i ) {
const value = pixels . readUInt8 ( i * 4 + index ) ;
2017-07-29 13:23:33 -04:00
sourceChannel . writeUInt8 ( value , i ) ;
}
if ( sourcePixelsLength > targetPixelsLength ) {
resizeChannel ( sourceChannel , sourceWidth , sourceHeight , targetChannel , targetWidth , targetHeight ) ;
}
return targetChannel ;
}
function writeChannel ( pixels , channel , index ) {
2019-02-05 20:59:09 -05:00
const pixelsLength = pixels . length / 4 ;
for ( let i = 0 ; i < pixelsLength ; ++ i ) {
const value = channel . readUInt8 ( i ) ;
2017-07-29 13:23:33 -04:00
pixels . writeUInt8 ( value , i * 4 + index ) ;
}
}
function getMinimumDimensions ( textures , options ) {
2019-02-05 20:59:09 -05:00
let width = Number . POSITIVE _INFINITY ;
let height = Number . POSITIVE _INFINITY ;
const length = textures . length ;
for ( let i = 0 ; i < length ; ++ i ) {
const texture = textures [ i ] ;
2017-07-29 13:23:33 -04:00
width = Math . min ( texture . width , width ) ;
height = Math . min ( texture . height , height ) ;
}
2019-02-05 20:59:09 -05:00
for ( let i = 0 ; i < length ; ++ i ) {
const texture = textures [ i ] ;
2017-07-29 13:23:33 -04:00
if ( texture . width !== width || texture . height !== height ) {
options . logger ( 'Texture ' + texture . path + ' will be scaled from ' + texture . width + 'x' + texture . height + ' to ' + width + 'x' + height + '.' ) ;
}
}
return [ width , height ] ;
}
2018-01-03 20:41:25 -05:00
function isChannelSingleColor ( buffer ) {
2019-02-05 20:59:09 -05:00
const first = buffer . readUInt8 ( 0 ) ;
const length = buffer . length ;
for ( let i = 1 ; i < length ; ++ i ) {
2018-01-03 20:41:25 -05:00
if ( buffer [ i ] !== first ) {
return false ;
}
}
return true ;
}
function createDiffuseAlphaTexture ( diffuseTexture , alphaTexture , options ) {
2019-02-05 20:59:09 -05:00
const packDiffuse = defined ( diffuseTexture ) ;
const packAlpha = defined ( alphaTexture ) ;
2018-01-03 20:41:25 -05:00
if ( ! packDiffuse ) {
return undefined ;
}
if ( ! packAlpha ) {
return diffuseTexture ;
}
2019-07-22 18:39:30 -04:00
if ( diffuseTexture === alphaTexture ) {
2019-07-19 13:07:34 -04:00
return diffuseTexture ;
}
2018-01-03 20:41:25 -05:00
if ( ! defined ( diffuseTexture . pixels ) || ! defined ( alphaTexture . pixels ) ) {
options . logger ( 'Could not get decoded texture data for ' + diffuseTexture . path + ' or ' + alphaTexture . path + '. The material will be created without an alpha texture.' ) ;
return diffuseTexture ;
}
2019-02-05 20:59:09 -05:00
const packedTextures = [ diffuseTexture , alphaTexture ] ;
const dimensions = getMinimumDimensions ( packedTextures , options ) ;
const width = dimensions [ 0 ] ;
const height = dimensions [ 1 ] ;
const pixelsLength = width * height ;
const pixels = Buffer . alloc ( pixelsLength * 4 , 0xFF ) ; // Initialize with 4 channels
const scratchChannel = Buffer . alloc ( pixelsLength ) ;
2018-01-03 20:41:25 -05:00
// Write into the R, G, B channels
2019-02-05 20:59:09 -05:00
const redChannel = getTextureChannel ( diffuseTexture , 0 , width , height , scratchChannel ) ;
2018-01-03 20:41:25 -05:00
writeChannel ( pixels , redChannel , 0 ) ;
2019-02-05 20:59:09 -05:00
const greenChannel = getTextureChannel ( diffuseTexture , 1 , width , height , scratchChannel ) ;
2018-01-03 20:41:25 -05:00
writeChannel ( pixels , greenChannel , 1 ) ;
2019-02-05 20:59:09 -05:00
const blueChannel = getTextureChannel ( diffuseTexture , 2 , width , height , scratchChannel ) ;
2018-01-03 20:41:25 -05:00
writeChannel ( pixels , blueChannel , 2 ) ;
// First try reading the alpha component from the alpha channel, but if it is a single color read from the red channel instead.
2019-02-05 20:59:09 -05:00
let alphaChannel = getTextureChannel ( alphaTexture , 3 , width , height , scratchChannel ) ;
2018-01-03 20:41:25 -05:00
if ( isChannelSingleColor ( alphaChannel ) ) {
alphaChannel = getTextureChannel ( alphaTexture , 0 , width , height , scratchChannel ) ;
}
writeChannel ( pixels , alphaChannel , 3 ) ;
2019-02-05 20:59:09 -05:00
const texture = new Texture ( ) ;
2018-01-03 20:41:25 -05:00
texture . name = diffuseTexture . name ;
texture . extension = '.png' ;
texture . pixels = pixels ;
texture . width = width ;
texture . height = height ;
texture . transparent = true ;
return texture ;
}
2017-07-29 13:23:33 -04:00
function createMetallicRoughnessTexture ( metallicTexture , roughnessTexture , occlusionTexture , options ) {
if ( defined ( options . overridingTextures . metallicRoughnessOcclusionTexture ) ) {
return metallicTexture ;
}
2019-02-05 20:59:09 -05:00
const packMetallic = defined ( metallicTexture ) ;
const packRoughness = defined ( roughnessTexture ) ;
const packOcclusion = defined ( occlusionTexture ) && options . packOcclusion ;
2017-07-29 13:23:33 -04:00
if ( ! packMetallic && ! packRoughness ) {
return undefined ;
}
if ( packMetallic && ! defined ( metallicTexture . pixels ) ) {
options . logger ( 'Could not get decoded texture data for ' + metallicTexture . path + '. The material will be created without a metallicRoughness texture.' ) ;
return undefined ;
}
if ( packRoughness && ! defined ( roughnessTexture . pixels ) ) {
options . logger ( 'Could not get decoded texture data for ' + roughnessTexture . path + '. The material will be created without a metallicRoughness texture.' ) ;
return undefined ;
}
if ( packOcclusion && ! defined ( occlusionTexture . pixels ) ) {
options . logger ( 'Could not get decoded texture data for ' + occlusionTexture . path + '. The occlusion texture will not be packed in the metallicRoughness texture.' ) ;
return undefined ;
}
2019-02-05 20:59:09 -05:00
const packedTextures = [ metallicTexture , roughnessTexture , occlusionTexture ] . filter ( function ( texture ) {
2017-07-29 13:23:33 -04:00
return defined ( texture ) && defined ( texture . pixels ) ;
} ) ;
2019-02-05 20:59:09 -05:00
const dimensions = getMinimumDimensions ( packedTextures , options ) ;
const width = dimensions [ 0 ] ;
const height = dimensions [ 1 ] ;
const pixelsLength = width * height ;
const pixels = Buffer . alloc ( pixelsLength * 4 , 0xFF ) ; // Initialize with 4 channels, unused channels will be white
const scratchChannel = Buffer . alloc ( pixelsLength ) ;
2017-07-29 13:23:33 -04:00
if ( packMetallic ) {
// Write into the B channel
2019-02-05 20:59:09 -05:00
const metallicChannel = getTextureChannel ( metallicTexture , 0 , width , height , scratchChannel ) ;
2017-07-29 13:23:33 -04:00
writeChannel ( pixels , metallicChannel , 2 ) ;
}
if ( packRoughness ) {
// Write into the G channel
2019-02-05 20:59:09 -05:00
const roughnessChannel = getTextureChannel ( roughnessTexture , 0 , width , height , scratchChannel ) ;
2017-07-29 13:23:33 -04:00
writeChannel ( pixels , roughnessChannel , 1 ) ;
}
if ( packOcclusion ) {
// Write into the R channel
2019-02-05 20:59:09 -05:00
const occlusionChannel = getTextureChannel ( occlusionTexture , 0 , width , height , scratchChannel ) ;
2017-07-29 13:23:33 -04:00
writeChannel ( pixels , occlusionChannel , 0 ) ;
}
2019-02-05 20:59:09 -05:00
const length = packedTextures . length ;
const names = new Array ( length ) ;
for ( let i = 0 ; i < length ; ++ i ) {
2017-07-29 13:23:33 -04:00
names [ i ] = packedTextures [ i ] . name ;
}
2019-02-05 20:59:09 -05:00
const name = names . join ( '_' ) ;
2017-07-29 13:23:33 -04:00
2019-02-05 20:59:09 -05:00
const texture = new Texture ( ) ;
2017-07-29 13:23:33 -04:00
texture . name = name ;
texture . extension = '.png' ;
texture . pixels = pixels ;
texture . width = width ;
texture . height = height ;
return texture ;
}
function createSpecularGlossinessTexture ( specularTexture , glossinessTexture , options ) {
if ( defined ( options . overridingTextures . specularGlossinessTexture ) ) {
return specularTexture ;
}
2019-02-05 20:59:09 -05:00
const packSpecular = defined ( specularTexture ) ;
const packGlossiness = defined ( glossinessTexture ) ;
2017-07-29 13:23:33 -04:00
if ( ! packSpecular && ! packGlossiness ) {
return undefined ;
}
if ( packSpecular && ! defined ( specularTexture . pixels ) ) {
options . logger ( 'Could not get decoded texture data for ' + specularTexture . path + '. The material will be created without a specularGlossiness texture.' ) ;
return undefined ;
}
if ( packGlossiness && ! defined ( glossinessTexture . pixels ) ) {
options . logger ( 'Could not get decoded texture data for ' + glossinessTexture . path + '. The material will be created without a specularGlossiness texture.' ) ;
return undefined ;
}
2019-02-05 20:59:09 -05:00
const packedTextures = [ specularTexture , glossinessTexture ] . filter ( function ( texture ) {
2017-07-29 13:23:33 -04:00
return defined ( texture ) && defined ( texture . pixels ) ;
} ) ;
2019-02-05 20:59:09 -05:00
const dimensions = getMinimumDimensions ( packedTextures , options ) ;
const width = dimensions [ 0 ] ;
const height = dimensions [ 1 ] ;
const pixelsLength = width * height ;
const pixels = Buffer . alloc ( pixelsLength * 4 , 0xFF ) ; // Initialize with 4 channels, unused channels will be white
const scratchChannel = Buffer . alloc ( pixelsLength ) ;
2017-07-29 13:23:33 -04:00
if ( packSpecular ) {
// Write into the R, G, B channels
2019-02-05 20:59:09 -05:00
const redChannel = getTextureChannel ( specularTexture , 0 , width , height , scratchChannel ) ;
2017-07-29 13:23:33 -04:00
writeChannel ( pixels , redChannel , 0 ) ;
2019-02-05 20:59:09 -05:00
const greenChannel = getTextureChannel ( specularTexture , 1 , width , height , scratchChannel ) ;
2017-07-29 13:23:33 -04:00
writeChannel ( pixels , greenChannel , 1 ) ;
2019-02-05 20:59:09 -05:00
const blueChannel = getTextureChannel ( specularTexture , 2 , width , height , scratchChannel ) ;
2017-07-29 13:23:33 -04:00
writeChannel ( pixels , blueChannel , 2 ) ;
}
if ( packGlossiness ) {
// Write into the A channel
2019-02-05 20:59:09 -05:00
const glossinessChannel = getTextureChannel ( glossinessTexture , 0 , width , height , scratchChannel ) ;
2017-07-29 13:23:33 -04:00
writeChannel ( pixels , glossinessChannel , 3 ) ;
}
2019-02-05 20:59:09 -05:00
const length = packedTextures . length ;
const names = new Array ( length ) ;
for ( let i = 0 ; i < length ; ++ i ) {
2017-07-29 13:23:33 -04:00
names [ i ] = packedTextures [ i ] . name ;
}
2019-02-05 20:59:09 -05:00
const name = names . join ( '_' ) ;
2017-07-29 13:23:33 -04:00
2019-02-05 20:59:09 -05:00
const texture = new Texture ( ) ;
2017-07-29 13:23:33 -04:00
texture . name = name ;
texture . extension = '.png' ;
texture . pixels = pixels ;
texture . width = width ;
texture . height = height ;
return texture ;
}
function createSpecularGlossinessMaterial ( material , options ) {
2019-02-05 20:59:09 -05:00
const emissiveTexture = material . emissiveTexture ;
const normalTexture = material . normalTexture ;
const occlusionTexture = material . ambientTexture ;
const diffuseTexture = material . diffuseTexture ;
const alphaTexture = material . alphaTexture ;
const specularTexture = material . specularTexture ;
const glossinessTexture = material . specularShininessTexture ;
const specularGlossinessTexture = createSpecularGlossinessTexture ( specularTexture , glossinessTexture , options ) ;
const diffuseAlphaTexture = createDiffuseAlphaTexture ( diffuseTexture , alphaTexture , options ) ;
let emissiveFactor = material . emissiveColor . slice ( 0 , 3 ) ;
let diffuseFactor = material . diffuseColor ;
let specularFactor = material . specularColor . slice ( 0 , 3 ) ;
let glossinessFactor = material . specularShininess ;
2017-07-29 13:23:33 -04:00
if ( defined ( emissiveTexture ) ) {
emissiveFactor = [ 1.0 , 1.0 , 1.0 ] ;
}
2019-07-22 18:39:30 -04:00
if ( defined ( diffuseTexture ) ) {
2017-07-29 13:23:33 -04:00
diffuseFactor = [ 1.0 , 1.0 , 1.0 , 1.0 ] ;
}
if ( defined ( specularTexture ) ) {
specularFactor = [ 1.0 , 1.0 , 1.0 ] ;
}
if ( defined ( glossinessTexture ) ) {
glossinessFactor = 1.0 ;
}
2019-02-05 20:59:09 -05:00
let transparent = false ;
2018-01-03 20:41:25 -05:00
if ( defined ( alphaTexture ) ) {
transparent = true ;
} else {
2019-02-05 20:59:09 -05:00
const alpha = material . alpha ;
2018-01-03 20:41:25 -05:00
diffuseFactor [ 3 ] = alpha ;
transparent = alpha < 1.0 ;
}
2017-07-29 13:23:33 -04:00
2019-07-22 18:39:30 -04:00
if ( defined ( diffuseTexture ) ) {
transparent = transparent || diffuseTexture . transparent ;
2017-07-29 13:23:33 -04:00
}
2019-02-05 20:59:09 -05:00
const doubleSided = transparent ;
const alphaMode = transparent ? 'BLEND' : 'OPAQUE' ;
2017-07-29 13:23:33 -04:00
return {
name : material . name ,
extensions : {
KHR _materials _pbrSpecularGlossiness : {
2018-01-03 20:41:25 -05:00
diffuseTexture : diffuseAlphaTexture ,
2017-07-29 13:23:33 -04:00
specularGlossinessTexture : specularGlossinessTexture ,
diffuseFactor : diffuseFactor ,
specularFactor : specularFactor ,
glossinessFactor : glossinessFactor
}
} ,
emissiveTexture : emissiveTexture ,
normalTexture : normalTexture ,
occlusionTexture : occlusionTexture ,
emissiveFactor : emissiveFactor ,
alphaMode : alphaMode ,
doubleSided : doubleSided
} ;
}
function createMetallicRoughnessMaterial ( material , options ) {
2019-02-05 20:59:09 -05:00
const emissiveTexture = material . emissiveTexture ;
const normalTexture = material . normalTexture ;
let occlusionTexture = material . ambientTexture ;
const baseColorTexture = material . diffuseTexture ;
const alphaTexture = material . alphaTexture ;
const metallicTexture = material . specularTexture ;
const roughnessTexture = material . specularShininessTexture ;
const metallicRoughnessTexture = createMetallicRoughnessTexture ( metallicTexture , roughnessTexture , occlusionTexture , options ) ;
const diffuseAlphaTexture = createDiffuseAlphaTexture ( baseColorTexture , alphaTexture , options ) ;
2017-07-29 13:23:33 -04:00
if ( options . packOcclusion ) {
occlusionTexture = metallicRoughnessTexture ;
}
2019-02-05 20:59:09 -05:00
let emissiveFactor = material . emissiveColor . slice ( 0 , 3 ) ;
let baseColorFactor = material . diffuseColor ;
let metallicFactor = material . specularColor [ 0 ] ;
let roughnessFactor = material . specularShininess ;
2017-07-29 13:23:33 -04:00
if ( defined ( emissiveTexture ) ) {
emissiveFactor = [ 1.0 , 1.0 , 1.0 ] ;
}
2019-07-22 18:39:30 -04:00
if ( defined ( baseColorTexture ) ) {
2017-07-29 13:23:33 -04:00
baseColorFactor = [ 1.0 , 1.0 , 1.0 , 1.0 ] ;
}
if ( defined ( metallicTexture ) ) {
metallicFactor = 1.0 ;
}
if ( defined ( roughnessTexture ) ) {
roughnessFactor = 1.0 ;
}
2019-02-05 20:59:09 -05:00
let transparent = false ;
2018-01-03 20:41:25 -05:00
if ( defined ( alphaTexture ) ) {
transparent = true ;
} else {
2019-02-05 20:59:09 -05:00
const alpha = material . alpha ;
2018-01-03 20:41:25 -05:00
baseColorFactor [ 3 ] = alpha ;
transparent = alpha < 1.0 ;
}
2017-07-29 13:23:33 -04:00
2019-07-22 18:39:30 -04:00
if ( defined ( baseColorTexture ) ) {
transparent = transparent || baseColorTexture . transparent ;
2017-07-29 13:23:33 -04:00
}
2019-02-05 20:59:09 -05:00
const doubleSided = transparent ;
const alphaMode = transparent ? 'BLEND' : 'OPAQUE' ;
2017-07-29 13:23:33 -04:00
return {
name : material . name ,
pbrMetallicRoughness : {
2018-01-03 20:41:25 -05:00
baseColorTexture : diffuseAlphaTexture ,
2017-07-29 13:23:33 -04:00
metallicRoughnessTexture : metallicRoughnessTexture ,
baseColorFactor : baseColorFactor ,
metallicFactor : metallicFactor ,
roughnessFactor : roughnessFactor
} ,
emissiveTexture : emissiveTexture ,
normalTexture : normalTexture ,
occlusionTexture : occlusionTexture ,
emissiveFactor : emissiveFactor ,
alphaMode : alphaMode ,
doubleSided : doubleSided
} ;
}
function luminance ( color ) {
return color [ 0 ] * 0.2125 + color [ 1 ] * 0.7154 + color [ 2 ] * 0.0721 ;
}
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
// Textures are not converted for now
2019-02-05 20:59:09 -05:00
const specularIntensity = luminance ( material . specularColor ) ;
2017-07-29 13:23:33 -04:00
// Transform from 0-1000 range to 0-1 range. Then invert.
2019-02-05 20:59:09 -05:00
let roughnessFactor = material . specularShininess ;
2017-07-29 13:23:33 -04:00
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 ) {
roughnessFactor *= ( 1.0 - specularIntensity ) ;
}
2019-02-05 20:59:09 -05:00
const metallicFactor = 0.0 ;
2017-07-29 13:23:33 -04:00
material . specularColor = [ metallicFactor , metallicFactor , metallicFactor , 1.0 ] ;
material . specularShininess = roughnessFactor ;
2018-12-04 14:33:10 -05:00
}