Reputation: 165
I'm working with release R73. My current task is to fill an array with materials. The content of this array is supposed to be used later. That usage depends on all materials to me completely loaded.
By now I loop through an array of JSON information and call this code for every element:
TLoader.load(
BASE_ODB_URL + jsonMat.pic,
function (texture) {
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(jsonMat.scaleu, jsonMat.scalev);
Mat = new THREE.MeshLambertMaterial({
map : texture,
side : THREE.DoubleSide,
name : jsonMat.mname
});
THREEMatList.push(Mat);
},
function (xhr) {
}, //onProgress
function (xhr) {
Mat = new THREE.MeshLambertMaterial({
color : 0xff0000,
side : THREE.DoubleSide,
name : jsonMat.mname
});
THREEMatList.push(Mat);
}
)
TLoader is initialized earlier: var TLoader = new THREE.TextureLoader();
If a material isn't there, when it is needed, I get a fallback material. This was only intended as an error option. Is there a way to wait until .load() finishes?
Upvotes: 7
Views: 11106
Reputation: 1000
You can also use this simple helper if you need to load multiple textures before rendering your scene :
/**
*
* @param {Array} texturesSources - List of Strings that represent texture sources
* @returns {Array} Array containing a Promise for each source
*/
function getTextures (texturesSources) {
const loader = new THREE.TextureLoader()
return texturesSources.map(textureSource => {
return new Promise((resolve, reject) => {
loader.load(
textureSource,
texture => resolve(texture),
undefined, // onProgress callback not supported from r84
err => reject(err)
)
})
})
}
Then wrap your code using Promise.all
, allowing to fetch the sources in parallel, and fail properly by catching the error.
Example :
const texturesBasePath = '../assets/textures/'
const texturesSRC = [
'image1.jpg',
'image2.jpg',
'image3.jpg',
].map(texture => texturesBasePath + texture)
Promise.all(getTextures(texturesSRC))
.then(textures => {
// create your materials, meshs...
// render the scene
})
.catch(err => console.error(err))
Upvotes: 5
Reputation: 51
Threejs already provides a callback for all elements loaded - via the use of a LoadingManager. By default, TextureLoader uses the DefaultLoadingManager:
import {TextureLoader, DefaultLoadingManager} from './three.module.js';
const getTextures = ()=> new Promise((resolve, reject)=>{
const loader = new TextureLoader();
DefaultLoadingManager.onLoad = ()=>resolve(textures);
const textures = [
"image1.jpg",
"image2.jpg",
"image3.jpg"
].map(filename=>loader.load(filename));
});
getTextures().then(result=>console.log("We received,", result,"!"));
That's going to wait for all assets to load, though. If you want to be listening for a specific subset of assets loaded, you can do that by constructing a custom LoadingManager and passing it into your TextureLoader to manage different asset bundles separately:
import {TextureLoader, LoadingManager} from './three.module.js';
const getTextures = ()=> new Promise((resolve, reject)=>{
const manager = new LoadingManager(()=>resolve(textures));
const loader = new TextureLoader(manager);
const textures = [
"image1.jpg",
"image2.jpg",
"image3.jpg"
].map(filename=>loader.load(filename));
});
getTextures().then(result=>console.log("We received,", result,"!"));
Upvotes: 5
Reputation: 3837
A way to solve this is through Promise's as they are really the way forward and I personally think it's a shame TextureLoader
doesn't return a Promise
to start with (it would make this a little bit easier). However, do keep in mind that IE11 will need a polyfill as it lacks native support for promises.
Something like this should do it, where textureArray
represents your JSON data in an iterable Array
:
var allPromises = [];
textureArray.forEach( function( jsonMat ) {
allPromises.push( new Promise( function( resolve, reject ) {
TLoader.load(
BASE_ODB_URL + jsonMat.pic,
function( texture ) {
// Success callback of TextureLoader
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( jsonMat.scaleu, jsonMat.scalev );
var material = new THREE.MeshLambertMaterial({
map: texture,
side: THREE.DoubleSide,
name: jsonMat.mname
});
THREEMatList.push( material );
// We're done, so tell the promise it is complete
resolve( material );
},
function( xhr ) {
// Progress callback of TextureLoader
// ...
},
function( xhr ) {
// Failure callback of TextureLoader
// Reject the promise with the failure
reject( new Error( 'Could not load ' + jsonMat.pic ) );
}
);
}));
});
Promise.all( allPromises )
.then( function( arrayOfMaterials ) {
// All textures are now loaded, and this array
// contains all the materials that you created
}, function( error ) {
console.error( "Could not load all textures:", error );
});
So what's happening here is that one overarching Promise
is used to keep track of the status of the other Promise
s. Each texture being loaded is wrapped in a Promise
, which are added to allPromises
. Finally that entire set of promises is tested for success or failure, and at that point you know about the overall success or failure.
One important thing to keep in mind here is that the Promise.all
will only succeed if all the textures were loaded, and it will fail as soon as any one fails. If you need finer control than that you'll need a Promise
polyfill with more features than the Promise/A+ spec offers, such as RSVP.js. Or instead of reject()
ing the promise you could resolve()
it and handle the result gracefully.
Upvotes: 2