3

I am trying to manipulate (e.g. change position, scale, rotation) an object loaded using OBJLoader in Three.js. While this is easy to do so once, I cannot work out how to do this when I want e.g. during the animate loop, or anywhere outside of the initial load callback.

Here is my code:

function loadObj( path, name )
{
    var obj;
    var mtlLoader = new THREE.MTLLoader();
    mtlLoader.setPath( path );
    mtlLoader.load( name+".mtl", function( materials ) {
        materials.preload();

        var objLoader = new THREE.OBJLoader();
        objLoader.setMaterials( materials );
        objLoader.setPath( path );
        objLoader.load( name+".obj", function( object ) {
            obj = object;
            obj.position.x = 20;
            scene.add( obj );
        });
    });
    return obj;
}

var myObj = loadObj( "assets/", "test" );

myObj.position.y = 20;

The key points to note here are:

  • I can load it and manipulate it within the loop fine, and no errors are raised;
  • If I do the above, I'll get an error on the last line, which states: Cannot read property 'position' of undefined.
  • This error remains if I define obj outside the function (as a global), and then reference it accordingly.

I've tried similar code with the JSON loader, to the same results (I am able to manipulate it within the load, but not afterwards).

Ishara Madhawa
  • 3,549
  • 5
  • 24
  • 42
Jolyon
  • 65
  • 8
  • This is a question about `asynchronous` fetching of assets, like there have been countless before. Since the loader is asynchornous, the `object` will not exist before you its done, so executing things on it will fails. You need to move this to the callback or wrap it in a `Promise`. – somethinghere May 04 '18 at 13:34
  • @somethinghere pardon my inexperience, but how would you do so? Would you be able to link me to some relevant article? I'm rather new to javascript / three.js. – Jolyon May 04 '18 at 13:36
  • I have added an answer that shows how to wrap in a Promise. Just keep in mind that async requests (like loading something) never return something directly, and will always use a callback or a way to delay executing related code. – somethinghere May 04 '18 at 13:44

1 Answers1

5

Loaders in THREE.js are asynchronous, so the best way around this is to simply use a Promise, or use the new await. Heres the promise implementation. I simply wrapped all the THREE.js loader code inside the promise and call resolve at the end. Then just use .then to execute whenever the asynchronous request is done.

function loadObj( path, name ){
  
  var progress = console.log;

  return new Promise(function( resolve, reject ){
  
    var obj;
    var mtlLoader = new THREE.MTLLoader();
    
    mtlLoader.setPath( path );
    mtlLoader.load( name + ".mtl", function( materials ){
    
        materials.preload();
        
        var objLoader = new THREE.OBJLoader();
        
        objLoader.setMaterials( materials );
        objLoader.setPath( path );
        objLoader.load( name + ".obj", resolve, progress, reject );
        
    }, progress, reject );
   
  });
  
}

// This way you can use as many .then as you want

var myObjPromise = loadObj( "assets/", "test" );

myObjPromise.then(myObj => {
  
  scene.add( myObj );
  
  myObj.position.y = 20;
  
});

Update Corrected a little mistake where it would reject while onProgress. My Bad, read the THREE.js docs again and noticed that the order of load is url, onSuccess, onProgress, onError, so the final should be url, resolve, () => {}, reject.

somethinghere
  • 16,311
  • 2
  • 28
  • 42
  • Thank you for the very prompt answer. I've added this to my code and I get an error: `Uncaught (in promise)`, but I suspect that is because I am doing something stupid. Now that I know what the problem is, I can at least work towards fixing it properly. – Jolyon May 04 '18 at 13:48
  • @Jolyon Yeah the `uncaught` will have to do with an error while the promise was running, so it was rejected. I also added `reject` to handle any loaders that encounter errors. `Promise` is a really simple concept, have a read about it and it makes a lot of sense! – somethinghere May 04 '18 at 13:50
  • If I include a .catch after the .then, and log the error, I get continual `object ProgressEvent` errors. When I use `error.currentTarget` to see what it is, I get `object XMLHttpRequest`, yet `error.loaded/error.total` yields 1. I'm really not sure why this isn't working - it should be loaded yet the promise doesn't want to work. Any advice? – Jolyon May 05 '18 at 10:37
  • @Jolyon My bad, let me correct my answer... The thing is that where I used `reject` it is actually `onProgress`, so let me correct that... Sorry mate. Been a while since I read the docs on this. – somethinghere May 05 '18 at 10:58