2021-08-02 11:31:59 -04:00
"use strict" ;
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" ) ;
2019-02-05 20:59:09 -05:00
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 ) {
2021-08-02 11:31:59 -04: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 ,
2023-10-03 13:28:47 -04:00
overridingTextures . specularGlossinessTexture ,
2021-08-02 11:31:59 -04:00
) ;
const overridingSpecularShininessTexture = defaultValue (
overridingTextures . metallicRoughnessOcclusionTexture ,
2023-10-03 13:28:47 -04:00
overridingTextures . specularGlossinessTexture ,
2021-08-02 11:31:59 -04:00
) ;
const overridingAmbientTexture = defaultValue (
overridingTextures . metallicRoughnessOcclusionTexture ,
2023-10-03 13:28:47 -04:00
overridingTextures . occlusionTexture ,
2021-08-02 11:31:59 -04:00
) ;
const overridingNormalTexture = overridingTextures . normalTexture ;
const overridingDiffuseTexture = overridingTextures . baseColorTexture ;
const overridingEmissiveTexture = overridingTextures . emissiveTexture ;
const overridingAlphaTexture = overridingTextures . alphaTexture ;
// Textures that are packed into PBR textures need to be decoded first
const decodeOptions = {
decode : true ,
} ;
const diffuseTextureOptions = {
checkTransparency : options . checkTransparency ,
} ;
const ambientTextureOptions = defined ( overridingAmbientTexture )
? undefined
: options . packOcclusion
2024-01-02 08:57:05 -05:00
? decodeOptions
: undefined ;
2021-08-02 11:31:59 -04:00
const specularTextureOptions = defined ( overridingSpecularTexture )
? undefined
: decodeOptions ;
const specularShinessTextureOptions = defined (
2023-10-03 13:28:47 -04:00
overridingSpecularShininessTexture ,
2021-08-02 11:31:59 -04:00
)
? undefined
: decodeOptions ;
const emissiveTextureOptions = undefined ;
const normalTextureOptions = undefined ;
const alphaTextureOptions = {
decode : true ,
} ;
function createMaterial ( name ) {
material = new Material ( ) ;
material . name = name ;
material . specularShininess = options . metallicRoughness ? 1.0 : 0.0 ;
material . specularTexture = overridingSpecularTexture ;
material . specularShininessTexture = overridingSpecularShininessTexture ;
material . diffuseTexture = overridingDiffuseTexture ;
material . ambientTexture = overridingAmbientTexture ;
material . normalTexture = overridingNormalTexture ;
material . emissiveTexture = overridingEmissiveTexture ;
material . alphaTexture = overridingAlphaTexture ;
materials . push ( material ) ;
}
function normalizeTexturePath ( texturePath , mtlDirectory ) {
2023-10-05 22:13:00 -04:00
//Remove double quotes around the texture file if it exists
texturePath = texturePath . replace ( /^"(.+)"$/ , "$1" ) ;
2021-08-02 11:31:59 -04:00
// Removes texture options from texture name
// Assumes no spaces in texture name
const re = /-(bm|t|s|o|blendu|blendv|boost|mm|texres|clamp|imfchan|type)/ ;
if ( re . test ( texturePath ) ) {
texturePath = texturePath . split ( /\s+/ ) . pop ( ) ;
}
texturePath = texturePath . replace ( /\\/g , "/" ) ;
return path . normalize ( path . resolve ( mtlDirectory , texturePath ) ) ;
}
function parseLine ( line ) {
line = line . trim ( ) ;
if ( /^newmtl/i . test ( line ) ) {
const name = line . substring ( 7 ) . trim ( ) ;
createMaterial ( name ) ;
} 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 . emissiveColor = [
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 = correctAlpha ( parseFloat ( value ) ) ;
} else if ( /^Tr /i . test ( line ) ) {
value = line . substring ( 3 ) . trim ( ) ;
material . alpha = correctAlpha ( 1.0 - parseFloat ( value ) ) ;
} else if ( /^map_Ka /i . test ( line ) ) {
if ( ! defined ( overridingAmbientTexture ) ) {
material . ambientTexture = normalizeTexturePath (
line . substring ( 7 ) . trim ( ) ,
2023-10-03 13:28:47 -04:00
mtlDirectory ,
2021-08-02 11:31:59 -04:00
) ;
}
} else if ( /^map_Ke /i . test ( line ) ) {
if ( ! defined ( overridingEmissiveTexture ) ) {
material . emissiveTexture = normalizeTexturePath (
line . substring ( 7 ) . trim ( ) ,
2023-10-03 13:28:47 -04:00
mtlDirectory ,
2021-08-02 11:31:59 -04:00
) ;
}
} else if ( /^map_Kd /i . test ( line ) ) {
if ( ! defined ( overridingDiffuseTexture ) ) {
material . diffuseTexture = normalizeTexturePath (
line . substring ( 7 ) . trim ( ) ,
2023-10-03 13:28:47 -04:00
mtlDirectory ,
2021-08-02 11:31:59 -04:00
) ;
}
} else if ( /^map_Ks /i . test ( line ) ) {
if ( ! defined ( overridingSpecularTexture ) ) {
material . specularTexture = normalizeTexturePath (
line . substring ( 7 ) . trim ( ) ,
2023-10-03 13:28:47 -04:00
mtlDirectory ,
2021-08-02 11:31:59 -04:00
) ;
}
} else if ( /^map_Ns /i . test ( line ) ) {
if ( ! defined ( overridingSpecularShininessTexture ) ) {
material . specularShininessTexture = normalizeTexturePath (
line . substring ( 7 ) . trim ( ) ,
2023-10-03 13:28:47 -04:00
mtlDirectory ,
2021-08-02 11:31:59 -04:00
) ;
}
} else if ( /^map_Bump /i . test ( line ) ) {
if ( ! defined ( overridingNormalTexture ) ) {
material . normalTexture = normalizeTexturePath (
line . substring ( 9 ) . trim ( ) ,
2023-10-03 13:28:47 -04:00
mtlDirectory ,
2021-08-02 11:31:59 -04:00
) ;
}
} else if ( /^map_d /i . test ( line ) ) {
if ( ! defined ( overridingAlphaTexture ) ) {
material . alphaTexture = normalizeTexturePath (
line . substring ( 6 ) . trim ( ) ,
2023-10-03 13:28:47 -04:00
mtlDirectory ,
2021-08-02 11:31:59 -04:00
) ;
}
}
}
function loadMaterialTextures ( material ) {
// If an alpha texture is present the diffuse texture needs to be decoded so they can be packed together
const diffuseAlphaTextureOptions = defined ( material . alphaTexture )
? alphaTextureOptions
: diffuseTextureOptions ;
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 ;
}
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 ) ;
2018-01-19 11:02:14 -05:00
}
2021-08-02 11:31:59 -04:00
const options = sharedOptions [ texturePath ] ;
options . checkTransparency =
options . checkTransparency || originalOptions . checkTransparency ;
options . decode = options . decode || originalOptions . decode ;
options . keepSource =
options . keepSource ||
! originalOptions . decode ||
! originalOptions . checkTransparency ;
}
} ) ;
2018-01-29 09:28:55 -05:00
2021-08-02 11:31:59 -04:00
textureNames . forEach ( function ( name ) {
const texturePath = material [ name ] ;
if ( defined ( texturePath ) ) {
loadMaterialTexture (
material ,
name ,
sharedOptions [ texturePath ] ,
mtlDirectory ,
texturePromiseMap ,
texturePromises ,
2023-10-03 13:28:47 -04:00
options ,
2021-08-02 11:31:59 -04:00
) ;
}
} ) ;
}
return readLines ( mtlPath , parseLine )
. then ( function ( ) {
const length = materials . length ;
for ( let i = 0 ; i < length ; ++ i ) {
loadMaterialTextures ( materials [ i ] ) ;
}
return Promise . all ( texturePromises ) ;
} )
. then ( function ( ) {
return convertMaterials ( materials , options ) ;
} ) ;
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 ) {
2021-08-02 11:31:59 -04:00
// 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-09-19 11:59:51 -04:00
}
2017-07-29 13:23:33 -04:00
function Material ( ) {
2021-08-02 11:31:59 -04:00
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
this . alphaTexture = undefined ; // map_d
2017-07-29 13:23:33 -04:00
}
2021-08-02 11:31:59 -04:00
loadMtl . getDefaultMaterial = function ( options ) {
return convertMaterial ( new Material ( ) , options ) ;
2017-07-29 13:23:33 -04:00
} ;
// Exposed for testing
2021-08-02 11:31:59 -04:00
loadMtl . _createMaterial = function ( materialOptions , options ) {
return convertMaterial ( combine ( materialOptions , new Material ( ) ) , options ) ;
2017-07-29 13:23:33 -04:00
} ;
2021-08-02 11:31:59 -04:00
function loadMaterialTexture (
material ,
name ,
textureOptions ,
mtlDirectory ,
texturePromiseMap ,
texturePromises ,
2023-10-03 13:28:47 -04:00
options ,
2021-08-02 11:31:59 -04:00
) {
const texturePath = material [ name ] ;
if ( ! defined ( texturePath ) ) {
return ;
}
let texturePromise = texturePromiseMap [ texturePath ] ;
if ( ! defined ( texturePromise ) ) {
const shallowPath = path . join ( mtlDirectory , path . basename ( texturePath ) ) ;
if ( options . secure && outsideDirectory ( texturePath , mtlDirectory ) ) {
// Try looking for the texture in the same directory as the obj
options . logger (
2023-10-03 13:28:47 -04:00
"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." ,
) ;
texturePromise = loadTexture ( shallowPath , textureOptions ) . catch (
function ( error ) {
options . logger ( error . message ) ;
options . logger (
` Could not read texture file at ${ shallowPath } . This texture will be ignored ` ,
) ;
} ,
2021-08-02 11:31:59 -04:00
) ;
} else {
texturePromise = loadTexture ( texturePath , textureOptions )
. catch ( function ( error ) {
// Try looking for the texture in the same directory as the obj
options . logger ( error . message ) ;
options . logger (
2023-10-03 13:28:47 -04:00
` Could not read texture file at ${ texturePath } . Attempting to read the texture file from within the obj directory instead. ` ,
2021-08-02 11:31:59 -04:00
) ;
return loadTexture ( shallowPath , textureOptions ) ;
} )
. catch ( function ( error ) {
options . logger ( error . message ) ;
options . logger (
2023-10-03 13:28:47 -04:00
` Could not read texture file at ${ shallowPath } . This texture will be ignored. ` ,
2021-08-02 11:31:59 -04:00
) ;
} ) ;
2017-07-29 13:23:33 -04:00
}
2021-08-02 11:31:59 -04:00
texturePromiseMap [ texturePath ] = texturePromise ;
}
2017-07-29 13:23:33 -04:00
2021-08-02 11:31:59 -04:00
texturePromises . push (
texturePromise . then ( function ( texture ) {
material [ name ] = texture ;
2023-10-03 13:28:47 -04:00
} ) ,
2021-08-02 11:31:59 -04:00
) ;
2017-07-29 13:23:33 -04:00
}
function convertMaterial ( material , options ) {
2021-08-02 11:31:59 -04:00
if ( options . specularGlossiness ) {
return createSpecularGlossinessMaterial ( material , options ) ;
} else if ( options . metallicRoughness ) {
2017-07-29 13:23:33 -04:00
return createMetallicRoughnessMaterial ( material , options ) ;
2021-08-02 11:31:59 -04:00
}
// No material type specified, convert the material to metallic roughness
convertTraditionalToMetallicRoughness ( material ) ;
return createMetallicRoughnessMaterial ( material , options ) ;
2017-07-29 13:23:33 -04:00
}
function convertMaterials ( materials , options ) {
2021-08-02 11:31:59 -04:00
return materials . map ( function ( material ) {
return convertMaterial ( material , options ) ;
} ) ;
2017-07-29 13:23:33 -04:00
}
2021-08-02 11:31:59 -04:00
function resizeChannel (
sourcePixels ,
sourceWidth ,
sourceHeight ,
targetPixels ,
targetWidth ,
2023-10-03 13:28:47 -04:00
targetHeight ,
2021-08-02 11:31:59 -04:00
) {
// Nearest neighbor sampling
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 ) ;
targetPixels . writeUInt8 ( sourceValue , targetIndex ) ;
}
}
return targetPixels ;
2017-07-29 13:23:33 -04:00
}
2019-02-05 20:59:09 -05:00
let scratchResizeChannel ;
2017-07-29 13:23:33 -04:00
2021-08-02 11:31:59 -04:00
function getTextureChannel (
texture ,
index ,
targetWidth ,
targetHeight ,
2023-10-03 13:28:47 -04:00
targetChannel ,
2021-08-02 11:31:59 -04:00
) {
const pixels = texture . pixels ; // RGBA
const sourceWidth = texture . width ;
const sourceHeight = texture . height ;
const sourcePixelsLength = sourceWidth * sourceHeight ;
const targetPixelsLength = targetWidth * targetHeight ;
// Allocate the scratchResizeChannel on demand if the texture needs to be resized
let sourceChannel = targetChannel ;
if ( sourcePixelsLength > targetPixelsLength ) {
if (
! defined ( scratchResizeChannel ) ||
sourcePixelsLength > scratchResizeChannel . length
) {
scratchResizeChannel = Buffer . alloc ( sourcePixelsLength ) ;
}
sourceChannel = scratchResizeChannel ;
}
for ( let i = 0 ; i < sourcePixelsLength ; ++ i ) {
const value = pixels . readUInt8 ( i * 4 + index ) ;
sourceChannel . writeUInt8 ( value , i ) ;
}
if ( sourcePixelsLength > targetPixelsLength ) {
resizeChannel (
sourceChannel ,
sourceWidth ,
sourceHeight ,
targetChannel ,
targetWidth ,
2023-10-03 13:28:47 -04:00
targetHeight ,
2021-08-02 11:31:59 -04:00
) ;
}
return targetChannel ;
2017-07-29 13:23:33 -04:00
}
function writeChannel ( pixels , channel , index ) {
2021-08-02 11:31:59 -04:00
const pixelsLength = pixels . length / 4 ;
for ( let i = 0 ; i < pixelsLength ; ++ i ) {
const value = channel . readUInt8 ( i ) ;
pixels . writeUInt8 ( value , i * 4 + index ) ;
}
2017-07-29 13:23:33 -04:00
}
function getMinimumDimensions ( textures , options ) {
2021-08-02 11:31:59 -04: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 ] ;
width = Math . min ( texture . width , width ) ;
height = Math . min ( texture . height , height ) ;
}
for ( let i = 0 ; i < length ; ++ i ) {
const texture = textures [ i ] ;
if ( texture . width !== width || texture . height !== height ) {
options . logger (
2023-10-03 13:28:47 -04:00
` Texture ${ texture . path } will be scaled from ${ texture . width } x ${ texture . height } to ${ width } x ${ height } . ` ,
2021-08-02 11:31:59 -04:00
) ;
}
}
return [ width , height ] ;
2017-07-29 13:23:33 -04:00
}
2018-01-03 20:41:25 -05:00
function isChannelSingleColor ( buffer ) {
2021-08-02 11:31:59 -04:00
const first = buffer . readUInt8 ( 0 ) ;
const length = buffer . length ;
for ( let i = 1 ; i < length ; ++ i ) {
if ( buffer [ i ] !== first ) {
return false ;
}
}
return true ;
2018-01-03 20:41:25 -05:00
}
function createDiffuseAlphaTexture ( diffuseTexture , alphaTexture , options ) {
2021-08-02 11:31:59 -04:00
const packDiffuse = defined ( diffuseTexture ) ;
const packAlpha = defined ( alphaTexture ) ;
if ( ! packDiffuse ) {
return undefined ;
}
if ( ! packAlpha ) {
return diffuseTexture ;
}
if ( diffuseTexture === alphaTexture ) {
return diffuseTexture ;
}
if ( ! defined ( diffuseTexture . pixels ) || ! defined ( alphaTexture . pixels ) ) {
options . logger (
2023-10-03 13:28:47 -04:00
` Could not get decoded texture data for ${ diffuseTexture . path } or ${ alphaTexture . path } . The material will be created without an alpha texture. ` ,
2021-08-02 11:31:59 -04:00
) ;
return diffuseTexture ;
}
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 ) ;
// Write into the R, G, B channels
const redChannel = getTextureChannel (
diffuseTexture ,
0 ,
width ,
height ,
2023-10-03 13:28:47 -04:00
scratchChannel ,
2021-08-02 11:31:59 -04:00
) ;
writeChannel ( pixels , redChannel , 0 ) ;
const greenChannel = getTextureChannel (
diffuseTexture ,
1 ,
width ,
height ,
2023-10-03 13:28:47 -04:00
scratchChannel ,
2021-08-02 11:31:59 -04:00
) ;
writeChannel ( pixels , greenChannel , 1 ) ;
const blueChannel = getTextureChannel (
diffuseTexture ,
2 ,
width ,
height ,
2023-10-03 13:28:47 -04:00
scratchChannel ,
2021-08-02 11:31:59 -04: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.
let alphaChannel = getTextureChannel (
alphaTexture ,
3 ,
width ,
height ,
2023-10-03 13:28:47 -04:00
scratchChannel ,
2021-08-02 11:31:59 -04:00
) ;
if ( isChannelSingleColor ( alphaChannel ) ) {
alphaChannel = getTextureChannel (
alphaTexture ,
0 ,
width ,
height ,
2023-10-03 13:28:47 -04:00
scratchChannel ,
2021-08-02 11:31:59 -04:00
) ;
}
writeChannel ( pixels , alphaChannel , 3 ) ;
const texture = new Texture ( ) ;
texture . name = diffuseTexture . name ;
texture . extension = ".png" ;
texture . pixels = pixels ;
texture . width = width ;
texture . height = height ;
texture . transparent = true ;
return texture ;
}
2018-01-03 20:41:25 -05:00
2021-08-02 11:31:59 -04:00
function createMetallicRoughnessTexture (
metallicTexture ,
roughnessTexture ,
occlusionTexture ,
2023-10-03 13:28:47 -04:00
options ,
2021-08-02 11:31:59 -04:00
) {
if ( defined ( options . overridingTextures . metallicRoughnessOcclusionTexture ) ) {
return metallicTexture ;
}
const packMetallic = defined ( metallicTexture ) ;
const packRoughness = defined ( roughnessTexture ) ;
const packOcclusion = defined ( occlusionTexture ) && options . packOcclusion ;
if ( ! packMetallic && ! packRoughness ) {
return undefined ;
}
if ( packMetallic && ! defined ( metallicTexture . pixels ) ) {
options . logger (
2023-10-03 13:28:47 -04:00
` Could not get decoded texture data for ${ metallicTexture . path } . The material will be created without a metallicRoughness texture. ` ,
2021-08-02 11:31:59 -04:00
) ;
return undefined ;
}
if ( packRoughness && ! defined ( roughnessTexture . pixels ) ) {
options . logger (
2023-10-03 13:28:47 -04:00
` Could not get decoded texture data for ${ roughnessTexture . path } . The material will be created without a metallicRoughness texture. ` ,
2021-08-02 11:31:59 -04:00
) ;
return undefined ;
}
if ( packOcclusion && ! defined ( occlusionTexture . pixels ) ) {
options . logger (
2023-10-03 13:28:47 -04:00
` Could not get decoded texture data for ${ occlusionTexture . path } . The occlusion texture will not be packed in the metallicRoughness texture. ` ,
2021-08-02 11:31:59 -04:00
) ;
return undefined ;
}
const packedTextures = [
metallicTexture ,
roughnessTexture ,
occlusionTexture ,
] . filter ( function ( texture ) {
return defined ( texture ) && defined ( texture . pixels ) ;
} ) ;
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 ) ;
if ( packMetallic ) {
// Write into the B channel
const metallicChannel = getTextureChannel (
metallicTexture ,
0 ,
width ,
height ,
2023-10-03 13:28:47 -04:00
scratchChannel ,
2021-08-02 11:31:59 -04:00
) ;
writeChannel ( pixels , metallicChannel , 2 ) ;
}
if ( packRoughness ) {
// Write into the G channel
const roughnessChannel = getTextureChannel (
roughnessTexture ,
0 ,
width ,
height ,
2023-10-03 13:28:47 -04:00
scratchChannel ,
2021-08-02 11:31:59 -04:00
) ;
writeChannel ( pixels , roughnessChannel , 1 ) ;
}
if ( packOcclusion ) {
// Write into the R channel
const occlusionChannel = getTextureChannel (
occlusionTexture ,
0 ,
width ,
height ,
2023-10-03 13:28:47 -04:00
scratchChannel ,
2021-08-02 11:31:59 -04:00
) ;
writeChannel ( pixels , occlusionChannel , 0 ) ;
}
const length = packedTextures . length ;
const names = new Array ( length ) ;
for ( let i = 0 ; i < length ; ++ i ) {
names [ i ] = packedTextures [ i ] . name ;
}
const name = names . join ( "_" ) ;
const texture = new Texture ( ) ;
texture . name = name ;
texture . extension = ".png" ;
texture . pixels = pixels ;
texture . width = width ;
texture . height = height ;
return texture ;
}
2018-01-03 20:41:25 -05:00
2021-08-02 11:31:59 -04:00
function createSpecularGlossinessTexture (
specularTexture ,
glossinessTexture ,
2023-10-03 13:28:47 -04:00
options ,
2021-08-02 11:31:59 -04:00
) {
if ( defined ( options . overridingTextures . specularGlossinessTexture ) ) {
return specularTexture ;
}
const packSpecular = defined ( specularTexture ) ;
const packGlossiness = defined ( glossinessTexture ) ;
if ( ! packSpecular && ! packGlossiness ) {
return undefined ;
}
if ( packSpecular && ! defined ( specularTexture . pixels ) ) {
options . logger (
2023-10-03 13:28:47 -04:00
` Could not get decoded texture data for ${ specularTexture . path } . The material will be created without a specularGlossiness texture. ` ,
2021-08-02 11:31:59 -04:00
) ;
return undefined ;
}
if ( packGlossiness && ! defined ( glossinessTexture . pixels ) ) {
options . logger (
2023-10-03 13:28:47 -04:00
` Could not get decoded texture data for ${ glossinessTexture . path } . The material will be created without a specularGlossiness texture. ` ,
2021-08-02 11:31:59 -04:00
) ;
return undefined ;
}
2023-10-03 13:28:47 -04:00
const packedTextures = [ specularTexture , glossinessTexture ] . filter (
function ( texture ) {
return defined ( texture ) && defined ( texture . pixels ) ;
} ,
) ;
2021-08-02 11:31:59 -04: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 ) ;
if ( packSpecular ) {
2018-01-03 20:41:25 -05:00
// Write into the R, G, B channels
2021-08-02 11:31:59 -04:00
const redChannel = getTextureChannel (
specularTexture ,
0 ,
width ,
height ,
2023-10-03 13:28:47 -04:00
scratchChannel ,
2021-08-02 11:31:59 -04:00
) ;
2018-01-03 20:41:25 -05:00
writeChannel ( pixels , redChannel , 0 ) ;
2021-08-02 11:31:59 -04:00
const greenChannel = getTextureChannel (
specularTexture ,
1 ,
width ,
height ,
2023-10-03 13:28:47 -04:00
scratchChannel ,
2021-08-02 11:31:59 -04:00
) ;
2018-01-03 20:41:25 -05:00
writeChannel ( pixels , greenChannel , 1 ) ;
2021-08-02 11:31:59 -04:00
const blueChannel = getTextureChannel (
specularTexture ,
2 ,
width ,
height ,
2023-10-03 13:28:47 -04:00
scratchChannel ,
2021-08-02 11:31:59 -04:00
) ;
2018-01-03 20:41:25 -05:00
writeChannel ( pixels , blueChannel , 2 ) ;
2021-08-02 11:31:59 -04:00
}
if ( packGlossiness ) {
// Write into the A channel
const glossinessChannel = getTextureChannel (
glossinessTexture ,
0 ,
width ,
height ,
2023-10-03 13:28:47 -04:00
scratchChannel ,
2021-08-02 11:31:59 -04:00
) ;
writeChannel ( pixels , glossinessChannel , 3 ) ;
}
const length = packedTextures . length ;
const names = new Array ( length ) ;
for ( let i = 0 ; i < length ; ++ i ) {
names [ i ] = packedTextures [ i ] . name ;
}
const name = names . join ( "_" ) ;
const texture = new Texture ( ) ;
texture . name = name ;
texture . extension = ".png" ;
texture . pixels = pixels ;
texture . width = width ;
texture . height = height ;
return texture ;
2017-07-29 13:23:33 -04:00
}
function createSpecularGlossinessMaterial ( material , options ) {
2021-08-02 11:31:59 -04: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 ,
2023-10-03 13:28:47 -04:00
options ,
2021-08-02 11:31:59 -04:00
) ;
const diffuseAlphaTexture = createDiffuseAlphaTexture (
diffuseTexture ,
alphaTexture ,
2023-10-03 13:28:47 -04:00
options ,
2021-08-02 11:31:59 -04:00
) ;
let emissiveFactor = material . emissiveColor . slice ( 0 , 3 ) ;
let diffuseFactor = material . diffuseColor ;
let specularFactor = material . specularColor . slice ( 0 , 3 ) ;
let 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 ( specularTexture ) ) {
specularFactor = [ 1.0 , 1.0 , 1.0 ] ;
}
if ( defined ( glossinessTexture ) ) {
glossinessFactor = 1.0 ;
}
let transparent = false ;
if ( defined ( alphaTexture ) ) {
transparent = true ;
} else {
const alpha = material . alpha ;
diffuseFactor [ 3 ] = alpha ;
2023-10-03 09:27:12 -04:00
transparent = alpha < 1.0 ;
2021-08-02 11:31:59 -04:00
}
if ( defined ( diffuseTexture ) ) {
transparent = transparent || diffuseTexture . transparent ;
}
2023-10-03 09:27:12 -04:00
const doubleSided = transparent || options . doubleSidedMaterial ;
2021-08-02 11:31:59 -04:00
const alphaMode = transparent ? "BLEND" : "OPAQUE" ;
return {
name : material . name ,
extensions : {
KHR _materials _pbrSpecularGlossiness : {
diffuseTexture : diffuseAlphaTexture ,
specularGlossinessTexture : specularGlossinessTexture ,
diffuseFactor : diffuseFactor ,
specularFactor : specularFactor ,
glossinessFactor : glossinessFactor ,
} ,
} ,
emissiveTexture : emissiveTexture ,
normalTexture : normalTexture ,
occlusionTexture : occlusionTexture ,
emissiveFactor : emissiveFactor ,
alphaMode : alphaMode ,
doubleSided : doubleSided ,
} ;
2017-07-29 13:23:33 -04:00
}
function createMetallicRoughnessMaterial ( material , options ) {
2021-08-02 11:31:59 -04: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 ,
2023-10-03 13:28:47 -04:00
options ,
2021-08-02 11:31:59 -04:00
) ;
const diffuseAlphaTexture = createDiffuseAlphaTexture (
baseColorTexture ,
alphaTexture ,
2023-10-03 13:28:47 -04:00
options ,
2021-08-02 11:31:59 -04:00
) ;
if ( options . packOcclusion ) {
occlusionTexture = metallicRoughnessTexture ;
}
let emissiveFactor = material . emissiveColor . slice ( 0 , 3 ) ;
let baseColorFactor = material . diffuseColor ;
let metallicFactor = material . specularColor [ 0 ] ;
let roughnessFactor = material . specularShininess ;
if ( defined ( emissiveTexture ) ) {
emissiveFactor = [ 1.0 , 1.0 , 1.0 ] ;
}
if ( defined ( baseColorTexture ) ) {
baseColorFactor = [ 1.0 , 1.0 , 1.0 , 1.0 ] ;
}
if ( defined ( metallicTexture ) ) {
metallicFactor = 1.0 ;
}
if ( defined ( roughnessTexture ) ) {
roughnessFactor = 1.0 ;
}
let transparent = false ;
if ( defined ( alphaTexture ) ) {
transparent = true ;
} else {
const alpha = material . alpha ;
baseColorFactor [ 3 ] = alpha ;
2023-10-03 09:27:12 -04:00
transparent = alpha < 1.0 ;
2021-08-02 11:31:59 -04:00
}
if ( defined ( baseColorTexture ) ) {
transparent = transparent || baseColorTexture . transparent ;
}
2023-10-03 09:27:12 -04:00
const doubleSided = transparent || options . doubleSidedMaterial ;
2021-08-02 11:31:59 -04:00
const alphaMode = transparent ? "BLEND" : "OPAQUE" ;
return {
name : material . name ,
pbrMetallicRoughness : {
baseColorTexture : diffuseAlphaTexture ,
metallicRoughnessTexture : metallicRoughnessTexture ,
baseColorFactor : baseColorFactor ,
metallicFactor : metallicFactor ,
roughnessFactor : roughnessFactor ,
} ,
emissiveTexture : emissiveTexture ,
normalTexture : normalTexture ,
occlusionTexture : occlusionTexture ,
emissiveFactor : emissiveFactor ,
alphaMode : alphaMode ,
doubleSided : doubleSided ,
} ;
2017-07-29 13:23:33 -04:00
}
function luminance ( color ) {
2021-08-02 11:31:59 -04:00
return color [ 0 ] * 0.2125 + color [ 1 ] * 0.7154 + color [ 2 ] * 0.0721 ;
2017-07-29 13:23:33 -04:00
}
function convertTraditionalToMetallicRoughness ( material ) {
2021-08-02 11:31:59 -04:00
// 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
const specularIntensity = luminance ( material . specularColor ) ;
// Transform from 0-1000 range to 0-1 range. Then invert.
let roughnessFactor = material . 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 ) {
roughnessFactor *= 1.0 - specularIntensity ;
}
const metallicFactor = 0.0 ;
material . specularColor = [
metallicFactor ,
metallicFactor ,
metallicFactor ,
1.0 ,
] ;
material . specularShininess = roughnessFactor ;
2018-12-04 14:33:10 -05:00
}