build( data, geometry
			/*, onProgress, onError */
		) {

			const materials = [];
			const textures = {};
			this.textureLoader.setCrossOrigin( this.crossOrigin ); // materials

			for ( let i = 0; i < data.metadata.materialCount; i ++ ) {

				const material = data.materials[ i ];
				const params = {
					userData: {
						MMD: {}
					}
				};
				if ( material.name !== undefined ) params.name = material.name;
				/*
      	 * THREE.Color
      	 *
      	 * MMD         MMDToonMaterial
      	 * ambient  -  emissive * a
      	 *               (a = 1.0 without map texture or 0.2 with map texture)
      	 *
      	 * MMDToonMaterial doesn't have ambient. Set it to emissive instead.
      	 * It'll be too bright if material has map texture so using coef 0.2.
      	 */

				params.diffuse = new THREE.Color().fromArray( material.diffuse );
				params.opacity = material.diffuse[ 3 ];
				params.specular = new THREE.Color().fromArray( material.specular );
				params.shininess = material.shininess;
				params.emissive = new THREE.Color().fromArray( material.ambient );
				params.transparent = params.opacity !== 1.0; //

				params.morphTargets = geometry.morphTargets.length > 0 ? true : false;
				params.fog = true; // blend

				params.blending = THREE.CustomBlending;
				params.blendSrc = THREE.SrcAlphaFactor;
				params.blendDst = THREE.OneMinusSrcAlphaFactor;
				params.blendSrcAlpha = THREE.SrcAlphaFactor;
				params.blendDstAlpha = THREE.DstAlphaFactor; // side

				if ( data.metadata.format === 'pmx' && ( material.flag & 0x1 ) === 1 ) {

					params.side = THREE.DoubleSide;

				} else {

					params.side = params.opacity === 1.0 ? THREE.FrontSide : THREE.DoubleSide;

				}

				if ( data.metadata.format === 'pmd' ) {

					// map, envMap
					if ( material.fileName ) {

						const fileName = material.fileName;
						const fileNames = fileName.split( '*' ); // fileNames[ 0 ]: mapFileName
						// fileNames[ 1 ]: envMapFileName( optional )

						params.map = this._loadTexture( fileNames[ 0 ], textures );

						if ( fileNames.length > 1 ) {

							const extension = fileNames[ 1 ].slice( - 4 ).toLowerCase();
							params.envMap = this._loadTexture( fileNames[ 1 ], textures );
							params.combine = extension === '.sph' ? THREE.MultiplyOperation : THREE.AddOperation;

						}

					} // gradientMap


					const toonFileName = material.toonIndex === - 1 ? 'toon00.bmp' : data.toonTextures[ material.toonIndex ].fileName;
					params.gradientMap = this._loadTexture( toonFileName, textures, {
						isToonTexture: true,
						isDefaultToonTexture: this._isDefaultToonTexture( toonFileName )
					} ); // parameters for OutlineEffect

					params.userData.outlineParameters = {
						thickness: material.edgeFlag === 1 ? 0.003 : 0.0,
						color: [ 0, 0, 0 ],
						alpha: 1.0,
						visible: material.edgeFlag === 1
					};

				} else {

					// map
					if ( material.textureIndex !== - 1 ) {

						params.map = this._loadTexture( data.textures[ material.textureIndex ], textures ); // Since PMX spec don't have standard to list map files except color map and env map,
						// we need to save file name for further mapping, like matching normal map file names after model loaded.
						// ref: https://gist.github.com/felixjones/f8a06bd48f9da9a4539f#texture

						params.userData.MMD.mapFileName = data.textures[ material.textureIndex ];

					} // envMap TODO: support m.envFlag === 3


					if ( material.envTextureIndex !== - 1 && ( material.envFlag === 1 || material.envFlag == 2 ) ) {

						params.matcap = this._loadTexture( data.textures[ material.envTextureIndex ], textures ); // Same as color map above, keep file name in userData for further usage.

						params.userData.MMD.matcapFileName = data.textures[ material.envTextureIndex ];
						params.matcapCombine = material.envFlag === 1 ? THREE.MultiplyOperation : THREE.AddOperation;

					} // gradientMap


					let toonFileName, isDefaultToon;

					if ( material.toonIndex === - 1 || material.toonFlag !== 0 ) {

						toonFileName = 'toon' + ( '0' + ( material.toonIndex + 1 ) ).slice( - 2 ) + '.bmp';
						isDefaultToon = true;

					} else {

						toonFileName = data.textures[ material.toonIndex ];
						isDefaultToon = false;

					}

					params.gradientMap = this._loadTexture( toonFileName, textures, {
						isToonTexture: true,
						isDefaultToonTexture: isDefaultToon
					} ); // parameters for OutlineEffect

					params.userData.outlineParameters = {
						thickness: material.edgeSize / 300,
						// TODO: better calculation?
						color: material.edgeColor.slice( 0, 3 ),
						alpha: material.edgeColor[ 3 ],
						visible: ( material.flag & 0x10 ) !== 0 && material.edgeSize > 0.0
					};

				}

				if ( params.map !== undefined ) {

					if ( ! params.transparent ) {

						this._checkImageTransparency( params.map, geometry, i );

					}

					params.emissive.multiplyScalar( 0.2 );

				}

				materials.push( new MMDToonMaterial( params ) );

			}

			if ( data.metadata.format === 'pmx' ) {

				// set transparent true if alpha morph is defined.
				function checkAlphaMorph( elements, materials ) {

					for ( let i = 0, il = elements.length; i < il; i ++ ) {

						const element = elements[ i ];
						if ( element.index === - 1 ) continue;
						const material = materials[ element.index ];

						if ( material.opacity !== element.diffuse[ 3 ] ) {

							material.transparent = true;

						}

					}

				}

				for ( let i = 0, il = data.morphs.length; i < il; i ++ ) {

					const morph = data.morphs[ i ];
					const elements = morph.elements;

					if ( morph.type === 0 ) {

						for ( let j = 0, jl = elements.length; j < jl; j ++ ) {

							const morph2 = data.morphs[ elements[ j ].index ];
							if ( morph2.type !== 8 ) continue;
							checkAlphaMorph( morph2.elements, materials );

						}

					} else if ( morph.type === 8 ) {

						checkAlphaMorph( elements, materials );

					}

				}

			}

			return materials;

		}
@param {Object} data - parsed PMD/PMX data
@param {BufferGeometry} geometry - some properties are dependend on geometry
@param {function} onProgress
@param {function} onError
@return {Array<MMDToonMaterial>}