function flatten( array, nBlocks, blockSize ) {

	const firstElem = array[ 0 ];

	if ( firstElem <= 0 || firstElem > 0 ) return array;
	// unoptimized: ! isNaN( firstElem )
	// see http://jacksondunstan.com/articles/983

	const n = nBlocks * blockSize;
	let r = arrayCacheF32[ n ];

	if ( r === undefined ) {

		r = new Float32Array( n );
		arrayCacheF32[ n ] = r;

	}

	if ( nBlocks !== 0 ) {

		firstElem.toArray( r, 0 );

		for ( let i = 1, offset = 0; i !== nBlocks; ++ i ) {

			offset += blockSize;
			array[ i ].toArray( r, offset );

		}

	}

	return r;

}
Uniforms of a program.
Those form a tree structure with a special top-level container for the root,
which you get by calling 'new WebGLUniforms( gl, program )'.


Properties of inner nodes including the top-level container:

.seq - array of nested uniforms
.map - nested uniforms by name


Methods of all nodes except the top-level container:

.setValue( gl, value, [textures] )

		uploads a uniform value(s)
 	the 'textures' parameter is needed for sampler uniforms


Static methods of the top-level container (textures factorizations):

.upload( gl, seq, values, textures )

		sets uniforms in 'seq' to 'values[id].value'

.seqWithValue( seq, values ) : filteredSeq

		filters 'seq' entries with corresponding entry in values


Methods of the top-level container (textures factorizations):

.setValue( gl, name, value, textures )

		sets uniform with  name 'name' to 'value'

.setOptional( gl, obj, prop )

		like .set for an optional property of the object