GLTFLoader goes through the following steps for .gltf assets.
.gltf JSON manifest.bin and texture resourcesonLoad callbackAll three of those steps are asynchronous, but LoadingManager invokes its on onLoad after any file request finishes if there are no outstanding requests. So the manager calls onLoad after steps (1) and (2), both. The issue is still there for .glb, because assembly is still asynchronous.
COLLADALoader gets around this because its parsing step is synchronous. As soon as the .dae file is obtained, it synchronously parses, calls the loader onLoad, and then the manager's onLoad is invoked. Texture requests will finish some time after that.
That approach isn't an option for glTF — we could hack a solution:
const fakeRequestURL = '[[my-fake-request-232932929]]';
manager.itemStart( fakeRequestURL );
// ... do async requests and assembly ...
manager.itemEnd( fakeRequestURL );
This makes itemsLoaded / itemsTotal incorrect, but isn't the worst outcome. Otherwise I think we'd need to change the LoadingManager API a bit.
This makes itemsLoaded / itemsTotal incorrect, but isn't the worst outcome.
In fact the issue is related to the GLTFLoader.
I always test the loaders in OFFLINE mode with Firefox.
Sea3D, OBJ, DAE work just fine.
Only the GLTFLoader is not able to pass xhr.loaded and xhr.total in OFFLINE mode.
This cannot be fixed just by editing GLTFLoader, as I mention above, unless we add fake itemStart/itemEnd calls. OBJ and DAE are simpler cases because they parse synchronously. I haven't checked Sea3D.
This cannot be fixed just by editing GLTFLoader, as I mention above, unless we add fake itemStart/itemEnd
Don,
At this moment SEA3D is better than GLTF from almost any point of view:
But the author is not as popular as Google ...
SEA3D: http://necromanthus.com/Test/html5/Lara.html
GLTF: http://necromanthus.com/Test/html5/Lara_gltf.html
Both exported from 3DS Max.
Both loaders run ok in ONLINE mode.
In OFFLINE mode only the Sea3D loader runs properly.
cheers
Offline mode is not related to this bug, and I have no interest in arguing about file formats here. If you'd like to report a new bug about offline mode please feel free.
Offline mode is not related to this bug, and I have no interest in arguing about file formats here. If you'd like to report a new bug about offline mode please feel free.
Please pay attention and don't mess up the THREE LoadingManager just to make the GLTF loader run "better".
That's all I ask here.
This issue is just filing a user-reported issue from the forums, and some details about the cause. If you believe I am messing something up please give feedback constructively, and until then please assume good intent, thanks.
EDIT: To clarify, when I say This makes itemsLoaded / itemsTotal incorrect... I mean the count would be incorrect _for glTF_, unless there is a better solution. I am not suggesting we break any existing loader.
Please consider that the tone of one's posts may be misinterpreted based on a language barrier. I believe that there is some frustration among the users who don't have stakes in other libraries, products and companies.
GLTFLoader definitely comes off as a first class citizen, and seems to be influencing many three.js core changes. This is a very valid concern:
Please pay attention and don't mess up the THREE LoadingManager just to make the GLTF loader run "better".
That's all I ask here.
Possibly could be reworded as:
Please proceed with care when refactoring
THREE.LoadingManagerand consider the fact thatGLTFLoaderis not the only loader present in/examples. Thank you.
Please consult other loaders for possible alternate patterns that might remove the requirement to modify
THREE.LoadingManager. Thank you.
I think it's fair to entertain assumptions other than need to refactor Foo. /loaders folder seems to contain 44 different files, plus two folders that also appear to be loaders (Sea3D is one of them). GLTFLoader makes up roughly 2% of that system. Other assumptions could be:
LoadingManager doesn't work with GLTFLoaderGLTFLoader could be refactoredFor example, either GLTFLoader could be made to work with GLTFLoadingManager or refactored if possible. Otherwise, it would be fair to explain why 2% of the loading examples should get preferential treatment, possibly outlining the scope of this influence (should GLTFLoader own the entire library, or just parts of it). I've seen it push for a complete Material systems overhaul of three.js, here it's the loading managers, it would be useful to define these extents. Then, should this be expanded to other loaders etc.
By no means am I suggesting that a loader should not warrant such a refactor, but I believe that this should be the last resort, after all the other reasonable avenues are explored. It would be good to establish some kind of a pattern for this process.
@donmccurdy For now, I think creating a proxy request via manager.itemStart() and manager.itemEnd() is the best solution for GLTFLoader.
Agreed. There is no need to refactor things, this is a pretty simple fix in GLTFLoader. Other asynchronous loaders may need to do the same, or may not, and if that is an issue we can revisit it.
I know this is a borderline support comment but I am using GLTFLoader with a LoadingManager and it is calling the OnLoad event after loading the .gltf file but then when I put a console.log() tracers it seems like it is then still doing more work to asynchronously load the actual model?
Is that what is being discussed here? I am pretty new to JavaScript and appreciate I don't understand then nuances properly but it seems like there is no clean way that I can find to know when I can start working with the models, other than checking in the update loop every time if the data is available which feels really dirty.
This is the console output I am seeing:
main.js:Main():model load begin
boxManager.js:62 boxManager.js:setupBoxes():about to Model.load() box asset
boxManager.js:64 boxManager.js:setupBoxes():about to push box
boxManager.js:62 boxManager.js:setupBoxes():about to Model.load() box asset
boxManager.js:64 boxManager.js:setupBoxes():about to push box
boxManager.js:62 boxManager.js:setupBoxes():about to Model.load() box asset
boxManager.js:64 boxManager.js:setupBoxes():about to push box
boxManager.js:62 boxManager.js:setupBoxes():about to Model.load() box asset
boxManager.js:64 boxManager.js:setupBoxes():about to push box
boxManager.js:62 boxManager.js:setupBoxes():about to Model.load() box asset
boxManager.js:64 boxManager.js:setupBoxes():about to push box
boxManager.js:62 boxManager.js:setupBoxes():about to Model.load() box asset
boxManager.js:64 boxManager.js:setupBoxes():about to push box
main.js:148 main.js:Main():model load completed
6 helpers.js:49 100% downloaded
main.js:152 ./assets/models/second-cardboard-box.gltf: 1 2
main.js:152 ./assets/models/second-cardboard-box.bin: 2 2
main.js:168 Everything is now fully loaded
6 boxManager.js:73 boxManager.js:test_obj_available():why is item.obj null?
6 model.js:91 Model.js:load():about to add group
"Everything is now fully loaded" should be the last part of the LoadingManager finishing, but its not finished and I cannot find another clean way to confirm that all models have finished loading.
So, I am really sorry to have dropped into this conversation but it seems closest to what I'm experiencing and if the answer is that it's a known bug then maybe I can finally get some sleep tonight.
Update: further research has lead me to the thread that started this issue off (always the same when I have stepped up to publically embarrass myself with a silly question) so it seems like I am experiencing the same thing. Would appreciate if anyone else can confirm from what I have described.
Seems the solution could be to use
Hi @rtpHarry — that is the issue described here, yes. If you use the loader's callback...
var loader = new THREE.GLTFLoader();
loader.load('foo.gltf', (gltf) => {
console.log(gltf.scene);
}, undefined, (e) => console.error(e));
... then you can safely assume the model is fully useable when the callback fires. Unfortunately the LoadingManager's onLoad callback is currently useless with a glTF model. If you need a temporary workaround until this is fixed, I think you can do this:
var loader = new THREE.GLTFLoader(manager);
manager.itemStart( 'foo' );
loader.load('foo.gltf', (gltf) => {
manager.itemEnd( 'foo' );
console.log(gltf.scene);
}, undefined, (e) => console.error(e));
manager.itemStart( 'bar' );
loader.load('bar.gltf', (gltf) => {
manager.itemEnd( 'bar' );
console.log(gltf.scene);
}, undefined, (e) => console.error(e));
// ...
manager.onLoad = function () {
console.log('everything is done');
};
Thank you so much for this, I thought that it was either my ignorance of JS or the fact that I'm using an ES6 boilerplate so I have been fishing through console logs and adjusting code on and off for the last three days.
@donmccurdy is my manager supposed to execute onLoad once? I actually have a bunch of gltf files that I loop over to load. Somehow the managers onLoad() executes every time a pair of .gltf and .bin files are loaded. I need an event after all my files have been loaded.
`class Loader {
static GLTF_PATH = "assets/gltf/";
static GLTF_ASSETS = ["suzanne", "suzanne_2"];
static GLTF_INDEX = 0;
constructor() {
this.manager = new THREE.LoadingManager();
this.manager.onLoad = ::this.onLoad;
this.gltfLoader = new GLTFLoader(this.manager);
this.load();
}
onLoad() {
console.log('Loading complete!');
}
load() {
this.manager.itemStart('foo');
if (Loader.GLTF_INDEX >= Loader.GLTF_ASSETS.length) return;
this.gltfLoader.load(
`${Loader.GLTF_PATH}${Loader.GLTF_ASSETS[Loader.GLTF_INDEX]}.gltf`,
gltf => {
this.manager.itemEnd('bar');
Loader.GLTF_INDEX++;
this.load();
}
);
}
}`
@SlimMarten the loading manager's onLoad call will fire when an item finishes loading, if there is nothing else currently pending. It has no way of knowing that you're going to start loading something else after that, so in this case yes you'll get multiple events. You could start loading everything at the same time to resolve this, or just use native JS APIs like Promise.all() to handle the async logic.
@donmccurdy okay interesting: if i use a JSONLoader instead of the GLTF Loader the onLoad gets fired once! I guess I still dont get what the LoadingManager is good for if its not tracking the all over loading process since each loader has its own callbacks as soon as they are finished.
Instead of using promises, is there a way to fire a GLTFLoaders' load() method using an array of URLs instead of a single URL ?
Keep in mind that the fix for the original issue (#14408) has not been merged... whether chained loading will fire one or multiple onLoad events once that is fixed, I'm not sure. It seems a bit risky to rely on that behavior.
Using Promises would be easier to debug, IMO:
var pending = GLTF_ASSETS.map( ( asset ) => {
return new Promise((resolve, reject) => loader.load( asset, resolve, undefined, reject ));
} );
Promise.all( pending ).then( ( ...results ) => {
console.log( results[0].scene );
});
I don't understand completely, the onLoad event firing before a gltf (or glb, in my case) model has been fully loaded has NOT been fixed, has it?
(that said, using the manager as advised up there does work for me)
@cptSwing the fix (#14408) was merged and this issue is closed. If you're on a recent version of threejs, there should be no problem. If you still have trouble we'll need more info, I'm not sure whether you're talking about GLTFLoader's onLoad or the LoadingManager's.